Merge "Delete frameworks/base/voip use voip-common from frameworks/opt/net/voip"

This commit is contained in:
Wink Saville
2013-01-29 03:10:42 +00:00
committed by Android (Google) Code Review
39 changed files with 9 additions and 11353 deletions

View File

@@ -219,10 +219,7 @@ LOCAL_SRC_FILES += \
telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl \
telephony/java/com/android/internal/telephony/IWapPushManager.aidl \
wifi/java/android/net/wifi/IWifiManager.aidl \
wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \
voip/java/android/net/sip/ISipSession.aidl \
voip/java/android/net/sip/ISipSessionListener.aidl \
voip/java/android/net/sip/ISipService.aidl
wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
#
@@ -353,6 +350,8 @@ non_base_dirs := \
../../external/apache-http/src/org/apache/http \
../opt/telephony/src/java/android/telephony \
../opt/telephony/src/java/android/telephony/gsm \
../opt/net/voip/src/java/android/net/rtp \
../opt/net/voip/src/java/android/net/sip
# These are relative to frameworks/base
dirs_to_check_apis := \
@@ -405,6 +404,7 @@ framework_docs_LOCAL_JAVA_LIBRARIES := \
framework \
mms-common \
telephony-common \
voip-common \
framework_docs_LOCAL_MODULE_CLASS := JAVA_LIBRARIES
framework_docs_LOCAL_DROIDDOC_HTML_DIR := docs/html

View File

@@ -149,6 +149,11 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framew
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/view/IDisplayMagnificationController.P)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/view/IDisplayMagnificationMediator.java)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/view/IDisplayMagnificationMediator.P)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/voip)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/librtp_jni_intermediates)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/librtp_jni.so)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/lib/librtp_jni.so)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/symbols/system/lib/librtp_jni.so)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST

View File

@@ -1,146 +0,0 @@
/*
* 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.Arrays;
/**
* This class defines a collection of audio codecs to be used with
* {@link AudioStream}s. Their parameters are designed to be exchanged using
* Session Description Protocol (SDP). Most of the values listed here can be
* found in RFC 3551, while others are described in separated standards.
*
* <p>Few simple configurations are defined as public static instances for the
* convenience of direct uses. More complicated ones could be obtained using
* {@link #getCodec(int, String, String)}. For example, one can use the
* following snippet to create a mode-1-only AMR codec.</p>
* <pre>
* AudioCodec codec = AudioCodec.getCodec(100, "AMR/8000", "mode-set=1");
* </pre>
*
* @see AudioStream
*/
public class AudioCodec {
/**
* The RTP payload type of the encoding.
*/
public final int type;
/**
* The encoding parameters to be used in the corresponding SDP attribute.
*/
public final String rtpmap;
/**
* The format parameters to be used in the corresponding SDP attribute.
*/
public final String fmtp;
/**
* G.711 u-law audio codec.
*/
public static final AudioCodec PCMU = new AudioCodec(0, "PCMU/8000", null);
/**
* G.711 a-law audio codec.
*/
public static final AudioCodec PCMA = new AudioCodec(8, "PCMA/8000", null);
/**
* GSM Full-Rate audio codec, also known as GSM-FR, GSM 06.10, GSM, or
* simply FR.
*/
public static final AudioCodec GSM = new AudioCodec(3, "GSM/8000", null);
/**
* GSM Enhanced Full-Rate audio codec, also known as GSM-EFR, GSM 06.60, or
* simply EFR.
*/
public static final AudioCodec GSM_EFR = new AudioCodec(96, "GSM-EFR/8000", null);
/**
* Adaptive Multi-Rate narrowband audio codec, also known as AMR or AMR-NB.
* Currently CRC, robust sorting, and interleaving are not supported. See
* more details about these features in RFC 4867.
*/
public static final AudioCodec AMR = new AudioCodec(97, "AMR/8000", null);
private static final AudioCodec[] sCodecs = {GSM_EFR, AMR, GSM, PCMU, PCMA};
private AudioCodec(int type, String rtpmap, String fmtp) {
this.type = type;
this.rtpmap = rtpmap;
this.fmtp = fmtp;
}
/**
* Returns system supported audio codecs.
*/
public static AudioCodec[] getCodecs() {
return Arrays.copyOf(sCodecs, sCodecs.length);
}
/**
* Creates an AudioCodec according to the given configuration.
*
* @param type The payload type of the encoding defined in RTP/AVP.
* @param rtpmap The encoding parameters specified in the corresponding SDP
* attribute, or null if it is not available.
* @param fmtp The format parameters specified in the corresponding SDP
* attribute, or null if it is not available.
* @return The configured AudioCodec or {@code null} if it is not supported.
*/
public static AudioCodec getCodec(int type, String rtpmap, String fmtp) {
if (type < 0 || type > 127) {
return null;
}
AudioCodec hint = null;
if (rtpmap != null) {
String clue = rtpmap.trim().toUpperCase();
for (AudioCodec codec : sCodecs) {
if (clue.startsWith(codec.rtpmap)) {
String channels = clue.substring(codec.rtpmap.length());
if (channels.length() == 0 || channels.equals("/1")) {
hint = codec;
}
break;
}
}
} else if (type < 96) {
for (AudioCodec codec : sCodecs) {
if (type == codec.type) {
hint = codec;
rtpmap = codec.rtpmap;
break;
}
}
}
if (hint == null) {
return null;
}
if (hint == AMR && fmtp != null) {
String clue = fmtp.toLowerCase();
if (clue.contains("crc=1") || clue.contains("robust-sorting=1") ||
clue.contains("interleaving=")) {
return null;
}
}
return new AudioCodec(type, rtpmap, fmtp);
}
}

View File

@@ -1,206 +0,0 @@
/*
* 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 android.media.AudioManager;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* An AudioGroup is an audio hub for the speaker, the microphone, and
* {@link AudioStream}s. Each of these components can be logically turned on
* or off by calling {@link #setMode(int)} or {@link RtpStream#setMode(int)}.
* The AudioGroup will go through these components and process them one by one
* within its execution loop. The loop consists of four steps. First, for each
* AudioStream not in {@link RtpStream#MODE_SEND_ONLY}, decodes its incoming
* packets and stores in its buffer. Then, if the microphone is enabled,
* processes the recorded audio and stores in its buffer. Third, if the speaker
* is enabled, mixes all AudioStream buffers and plays back. Finally, for each
* AudioStream not in {@link RtpStream#MODE_RECEIVE_ONLY}, mixes all other
* buffers and sends back the encoded packets. An AudioGroup does nothing if
* there is no AudioStream in it.
*
* <p>Few things must be noticed before using these classes. The performance is
* highly related to the system load and the network bandwidth. Usually a
* simpler {@link AudioCodec} costs fewer CPU cycles but requires more network
* bandwidth, and vise versa. Using two AudioStreams at the same time doubles
* not only the load but also the bandwidth. The condition varies from one
* device to another, and developers should choose the right combination in
* order to get the best result.</p>
*
* <p>It is sometimes useful to keep multiple AudioGroups at the same time. For
* example, a Voice over IP (VoIP) application might want to put a conference
* call on hold in order to make a new call but still allow people in the
* conference call talking to each other. This can be done easily using two
* AudioGroups, but there are some limitations. Since the speaker and the
* microphone are globally shared resources, only one AudioGroup at a time is
* allowed to run in a mode other than {@link #MODE_ON_HOLD}. The others will
* be unable to acquire these resources and fail silently.</p>
*
* <p class="note">Using this class requires
* {@link android.Manifest.permission#RECORD_AUDIO} permission. Developers
* should set the audio mode to {@link AudioManager#MODE_IN_COMMUNICATION}
* using {@link AudioManager#setMode(int)} and change it back when none of
* the AudioGroups is in use.</p>
*
* @see AudioStream
*/
public class AudioGroup {
/**
* This mode is similar to {@link #MODE_NORMAL} except the speaker and
* the microphone are both disabled.
*/
public static final int MODE_ON_HOLD = 0;
/**
* This mode is similar to {@link #MODE_NORMAL} except the microphone is
* disabled.
*/
public static final int MODE_MUTED = 1;
/**
* This mode indicates that the speaker, the microphone, and all
* {@link AudioStream}s in the group are enabled. First, the packets
* received from the streams are decoded and mixed with the audio recorded
* from the microphone. Then, the results are played back to the speaker,
* encoded and sent back to each stream.
*/
public static final int MODE_NORMAL = 2;
/**
* This mode is similar to {@link #MODE_NORMAL} except the echo suppression
* is enabled. It should be only used when the speaker phone is on.
*/
public static final int MODE_ECHO_SUPPRESSION = 3;
private static final int MODE_LAST = 3;
private final Map<AudioStream, Integer> mStreams;
private int mMode = MODE_ON_HOLD;
private int mNative;
static {
System.loadLibrary("rtp_jni");
}
/**
* Creates an empty AudioGroup.
*/
public AudioGroup() {
mStreams = new HashMap<AudioStream, Integer>();
}
/**
* Returns the {@link AudioStream}s in this group.
*/
public AudioStream[] getStreams() {
synchronized (this) {
return mStreams.keySet().toArray(new AudioStream[mStreams.size()]);
}
}
/**
* Returns the current mode.
*/
public int getMode() {
return mMode;
}
/**
* Changes the current mode. It must be one of {@link #MODE_ON_HOLD},
* {@link #MODE_MUTED}, {@link #MODE_NORMAL}, and
* {@link #MODE_ECHO_SUPPRESSION}.
*
* @param mode The mode to change to.
* @throws IllegalArgumentException if the mode is invalid.
*/
public void setMode(int mode) {
if (mode < 0 || mode > MODE_LAST) {
throw new IllegalArgumentException("Invalid mode");
}
synchronized (this) {
nativeSetMode(mode);
mMode = mode;
}
}
private native void nativeSetMode(int mode);
// Package-private method used by AudioStream.join().
synchronized void add(AudioStream stream) {
if (!mStreams.containsKey(stream)) {
try {
AudioCodec codec = stream.getCodec();
String codecSpec = String.format(Locale.US, "%d %s %s", codec.type,
codec.rtpmap, codec.fmtp);
int id = nativeAdd(stream.getMode(), stream.getSocket(),
stream.getRemoteAddress().getHostAddress(),
stream.getRemotePort(), codecSpec, stream.getDtmfType());
mStreams.put(stream, id);
} catch (NullPointerException e) {
throw new IllegalStateException(e);
}
}
}
private native int nativeAdd(int mode, int socket, String remoteAddress,
int remotePort, String codecSpec, int dtmfType);
// Package-private method used by AudioStream.join().
synchronized void remove(AudioStream stream) {
Integer id = mStreams.remove(stream);
if (id != null) {
nativeRemove(id);
}
}
private native void nativeRemove(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 void sendDtmf(int event) {
if (event < 0 || event > 15) {
throw new IllegalArgumentException("Invalid event");
}
synchronized (this) {
nativeSendDtmf(event);
}
}
private native void nativeSendDtmf(int event);
/**
* Removes every {@link AudioStream} in this group.
*/
public void clear() {
for (AudioStream stream : getStreams()) {
stream.join(null);
}
}
@Override
protected void finalize() throws Throwable {
nativeRemove(0);
super.finalize();
}
}

View File

@@ -1,167 +0,0 @@
/*
* 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;
/**
* An AudioStream is a {@link RtpStream} which carrys audio payloads over
* Real-time Transport Protocol (RTP). Two different classes are developed in
* order to support various usages such as audio conferencing. An AudioStream
* represents a remote endpoint which consists of a network mapping and a
* configured {@link AudioCodec}. On the other side, An {@link AudioGroup}
* represents a local endpoint which mixes all the AudioStreams and optionally
* interacts with the speaker and the microphone at the same time. The simplest
* usage includes one for each endpoints. For other combinations, developers
* should be aware of the limitations described in {@link AudioGroup}.
*
* <p>An AudioStream becomes busy when it joins an AudioGroup. In this case most
* of the setter methods are disabled. This is designed to ease the task of
* managing native resources. One can always make an AudioStream leave its
* AudioGroup by calling {@link #join(AudioGroup)} with {@code null} and put it
* back after the modification is done.</p>
*
* <p class="note">Using this class requires
* {@link android.Manifest.permission#INTERNET} permission.</p>
*
* @see RtpStream
* @see AudioGroup
*/
public class AudioStream extends RtpStream {
private AudioCodec mCodec;
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 has already joined an
* {@link AudioGroup}.
*/
@Override
public final boolean isBusy() {
return mGroup != null;
}
/**
* Returns the joined {@link AudioGroup}.
*/
public AudioGroup getGroup() {
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) {
synchronized (this) {
if (mGroup == group) {
return;
}
if (mGroup != null) {
mGroup.remove(this);
mGroup = null;
}
if (group != null) {
group.add(this);
mGroup = group;
}
}
}
/**
* Returns the {@link AudioCodec}, or {@code null} if it is not set.
*
* @see #setCodec(AudioCodec)
*/
public AudioCodec getCodec() {
return mCodec;
}
/**
* Sets the {@link AudioCodec}.
*
* @param codec The AudioCodec to be used.
* @throws IllegalArgumentException if its type is used by DTMF.
* @throws IllegalStateException if the stream is busy.
*/
public void setCodec(AudioCodec codec) {
if (isBusy()) {
throw new IllegalStateException("Busy");
}
if (codec.type == mDtmfType) {
throw new IllegalArgumentException("The type is used by DTMF");
}
mCodec = codec;
}
/**
* Returns the RTP payload type for dual-tone multi-frequency (DTMF) digits,
* or {@code -1} if it is not enabled.
*
* @see #setDtmfType(int)
*/
public int getDtmfType() {
return mDtmfType;
}
/**
* 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 type. 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 (mCodec != null && type == mCodec.type) {
throw new IllegalArgumentException("The type is used by codec");
}
}
mDtmfType = type;
}
}

View File

@@ -1,195 +0,0 @@
/*
* 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 the base class of streams which send and receive network
* packets with media payloads over Real-time Transport Protocol (RTP).
*
* <p class="note">Using this class requires
* {@link android.Manifest.permission#INTERNET} permission.</p>
*/
public class RtpStream {
/**
* This mode indicates that the stream sends and receives packets at the
* same time. This is the initial mode for new streams.
*/
public static final int MODE_NORMAL = 0;
/**
* This mode indicates that the stream only sends packets.
*/
public static final int MODE_SEND_ONLY = 1;
/**
* This mode indicates that the stream only receives packets.
*/
public static final int MODE_RECEIVE_ONLY = 2;
private static final int MODE_LAST = 2;
private final InetAddress mLocalAddress;
private final int mLocalPort;
private InetAddress mRemoteAddress;
private int mRemotePort = -1;
private int mMode = MODE_NORMAL;
private int mSocket = -1;
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. In this case most of the
* setter methods are disabled. This method is intended to be overridden
* by subclasses.
*/
public boolean isBusy() {
return false;
}
/**
* Returns the current mode.
*/
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 < 0 || mode > MODE_LAST) {
throw new IllegalArgumentException("Invalid mode");
}
mMode = mode;
}
/**
* Associates with a remote host. This defines the destination of the
* outgoing packets.
*
* @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;
}
int getSocket() {
return mSocket;
}
/**
* Releases allocated resources. The stream becomes inoperable after calling
* this method.
*
* @throws IllegalStateException if the stream is busy.
* @see #isBusy()
*/
public void release() {
synchronized (this) {
if (isBusy()) {
throw new IllegalStateException("Busy");
}
close();
}
}
private native void close();
@Override
protected void finalize() throws Throwable {
close();
super.finalize();
}
}

View File

@@ -1,28 +0,0 @@
<html>
<body>
<p>Provides APIs for RTP (Real-time Transport Protocol), allowing applications to manage on-demand
or interactive data streaming. In particular, apps that provide VOIP, push-to-talk, conferencing,
and audio streaming can use these APIs to initiate sessions and transmit or receive data streams
over any available network.</p>
<p>To support audio conferencing and similar usages, you need to instantiate two classes as
endpoints for the stream:</p>
<ul>
<li>{@link android.net.rtp.AudioStream} specifies a remote endpoint and consists of network mapping
and a configured {@link android.net.rtp.AudioCodec}.</li>
<li>{@link android.net.rtp.AudioGroup} represents the local endpoint for one or more {@link
android.net.rtp.AudioStream}s. The {@link android.net.rtp.AudioGroup} mixes all the {@link
android.net.rtp.AudioStream}s and optionally interacts with the device speaker and the microphone at
the same time.</li>
</ul>
<p>The simplest usage involves a single remote endpoint and local endpoint. For more complex usages,
refer to the limitations described for {@link android.net.rtp.AudioGroup}.</p>
<p class="note"><strong>Note:</strong> To use the RTP APIs, you must request the {@link
android.Manifest.permission#INTERNET} and {@link
android.Manifest.permission#RECORD_AUDIO} permissions in your manifest file.</p>
</body>
</html>

View File

@@ -1,43 +0,0 @@
/*
* 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.app.PendingIntent;
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,
in PendingIntent incomingCallPendingIntent,
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();
}

View File

@@ -1,148 +0,0 @@
/*
* 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.SipProfile;
/**
* A SIP session that is associated with a SIP dialog or a transaction that is
* 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}.
*
* @return the session state
*/
int 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
* @param timeout the session will be timed out if the call is not
* established within {@code timeout} seconds
* @see ISipSessionListener
*/
void makeCall(in SipProfile callee, String sessionDescription, int timeout);
/**
* 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
* @param timeout the session will be timed out if the call is not
* established within {@code timeout} seconds
*/
void answerCall(String sessionDescription, int timeout);
/**
* 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
* @param timeout the session will be timed out if the call is not
* established within {@code timeout} seconds
*/
void changeCall(String sessionDescription, int timeout);
}

View File

@@ -1,133 +0,0 @@
/*
* 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,
String 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,
String 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 the call is being transferred to a new one.
*
* @param newSession the new session that the call will be transferred to
* @param sessionDescription the new peer's session description
*/
void onCallTransferring(in ISipSession newSession, String sessionDescription);
/**
* Called when an error occurs during session initialization and
* termination.
*
* @param session the session object that carries out the transaction
* @param errorCode error code defined in {@link SipErrorCode}
* @param errorMessage error message
*/
void onError(in ISipSession session, int errorCode, String errorMessage);
/**
* Called when an error occurs during session modification negotiation.
*
* @param session the session object that carries out the transaction
* @param errorCode error code defined in {@link SipErrorCode}
* @param errorMessage error message
*/
void onCallChangeFailed(in ISipSession session, int errorCode,
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 errorCode error code defined in {@link SipErrorCode}
* @param errorMessage error message
*/
void onRegistrationFailed(in ISipSession session, int errorCode,
String errorMessage);
/**
* Called when the registration gets timed out.
*
* @param session the session object that carries out the transaction
*/
void onRegistrationTimeout(in ISipSession session);
}

View File

@@ -1,613 +0,0 @@
/*
* 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 java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
/**
* An object used to manipulate messages of Session Description Protocol (SDP).
* It is mainly designed for the uses of Session Initiation Protocol (SIP).
* Therefore, it only handles connection addresses ("c="), bandwidth limits,
* ("b="), encryption keys ("k="), and attribute fields ("a="). Currently this
* implementation does not support multicast sessions.
*
* <p>Here is an example code to create a session description.</p>
* <pre>
* SimpleSessionDescription description = new SimpleSessionDescription(
* System.currentTimeMillis(), "1.2.3.4");
* Media media = description.newMedia("audio", 56789, 1, "RTP/AVP");
* media.setRtpPayload(0, "PCMU/8000", null);
* media.setRtpPayload(8, "PCMA/8000", null);
* media.setRtpPayload(127, "telephone-event/8000", "0-15");
* media.setAttribute("sendrecv", "");
* </pre>
* <p>Invoking <code>description.encode()</code> will produce a result like the
* one below.</p>
* <pre>
* v=0
* o=- 1284970442706 1284970442709 IN IP4 1.2.3.4
* s=-
* c=IN IP4 1.2.3.4
* t=0 0
* m=audio 56789 RTP/AVP 0 8 127
* a=rtpmap:0 PCMU/8000
* a=rtpmap:8 PCMA/8000
* a=rtpmap:127 telephone-event/8000
* a=fmtp:127 0-15
* a=sendrecv
* </pre>
* @hide
*/
public class SimpleSessionDescription {
private final Fields mFields = new Fields("voscbtka");
private final ArrayList<Media> mMedia = new ArrayList<Media>();
/**
* Creates a minimal session description from the given session ID and
* unicast address. The address is used in the origin field ("o=") and the
* connection field ("c="). See {@link SimpleSessionDescription} for an
* example of its usage.
*/
public SimpleSessionDescription(long sessionId, String address) {
address = (address.indexOf(':') < 0 ? "IN IP4 " : "IN IP6 ") + address;
mFields.parse("v=0");
mFields.parse(String.format(Locale.US, "o=- %d %d %s", sessionId,
System.currentTimeMillis(), address));
mFields.parse("s=-");
mFields.parse("t=0 0");
mFields.parse("c=" + address);
}
/**
* Creates a session description from the given message.
*
* @throws IllegalArgumentException if message is invalid.
*/
public SimpleSessionDescription(String message) {
String[] lines = message.trim().replaceAll(" +", " ").split("[\r\n]+");
Fields fields = mFields;
for (String line : lines) {
try {
if (line.charAt(1) != '=') {
throw new IllegalArgumentException();
}
if (line.charAt(0) == 'm') {
String[] parts = line.substring(2).split(" ", 4);
String[] ports = parts[1].split("/", 2);
Media media = newMedia(parts[0], Integer.parseInt(ports[0]),
(ports.length < 2) ? 1 : Integer.parseInt(ports[1]),
parts[2]);
for (String format : parts[3].split(" ")) {
media.setFormat(format, null);
}
fields = media;
} else {
fields.parse(line);
}
} catch (Exception e) {
throw new IllegalArgumentException("Invalid SDP: " + line);
}
}
}
/**
* Creates a new media description in this session description.
*
* @param type The media type, e.g. {@code "audio"}.
* @param port The first transport port used by this media.
* @param portCount The number of contiguous ports used by this media.
* @param protocol The transport protocol, e.g. {@code "RTP/AVP"}.
*/
public Media newMedia(String type, int port, int portCount,
String protocol) {
Media media = new Media(type, port, portCount, protocol);
mMedia.add(media);
return media;
}
/**
* Returns all the media descriptions in this session description.
*/
public Media[] getMedia() {
return mMedia.toArray(new Media[mMedia.size()]);
}
/**
* Encodes the session description and all its media descriptions in a
* string. Note that the result might be incomplete if a required field
* has never been added before.
*/
public String encode() {
StringBuilder buffer = new StringBuilder();
mFields.write(buffer);
for (Media media : mMedia) {
media.write(buffer);
}
return buffer.toString();
}
/**
* Returns the connection address or {@code null} if it is not present.
*/
public String getAddress() {
return mFields.getAddress();
}
/**
* Sets the connection address. The field will be removed if the address
* is {@code null}.
*/
public void setAddress(String address) {
mFields.setAddress(address);
}
/**
* Returns the encryption method or {@code null} if it is not present.
*/
public String getEncryptionMethod() {
return mFields.getEncryptionMethod();
}
/**
* Returns the encryption key or {@code null} if it is not present.
*/
public String getEncryptionKey() {
return mFields.getEncryptionKey();
}
/**
* Sets the encryption method and the encryption key. The field will be
* removed if the method is {@code null}.
*/
public void setEncryption(String method, String key) {
mFields.setEncryption(method, key);
}
/**
* Returns the types of the bandwidth limits.
*/
public String[] getBandwidthTypes() {
return mFields.getBandwidthTypes();
}
/**
* Returns the bandwidth limit of the given type or {@code -1} if it is not
* present.
*/
public int getBandwidth(String type) {
return mFields.getBandwidth(type);
}
/**
* Sets the bandwith limit for the given type. The field will be removed if
* the value is negative.
*/
public void setBandwidth(String type, int value) {
mFields.setBandwidth(type, value);
}
/**
* Returns the names of all the attributes.
*/
public String[] getAttributeNames() {
return mFields.getAttributeNames();
}
/**
* Returns the attribute of the given name or {@code null} if it is not
* present.
*/
public String getAttribute(String name) {
return mFields.getAttribute(name);
}
/**
* Sets the attribute for the given name. The field will be removed if
* the value is {@code null}. To set a binary attribute, use an empty
* string as the value.
*/
public void setAttribute(String name, String value) {
mFields.setAttribute(name, value);
}
/**
* This class represents a media description of a session description. It
* can only be created by {@link SimpleSessionDescription#newMedia}. Since
* the syntax is more restricted for RTP based protocols, two sets of access
* methods are implemented. See {@link SimpleSessionDescription} for an
* example of its usage.
*/
public static class Media extends Fields {
private final String mType;
private final int mPort;
private final int mPortCount;
private final String mProtocol;
private ArrayList<String> mFormats = new ArrayList<String>();
private Media(String type, int port, int portCount, String protocol) {
super("icbka");
mType = type;
mPort = port;
mPortCount = portCount;
mProtocol = protocol;
}
/**
* Returns the media type.
*/
public String getType() {
return mType;
}
/**
* Returns the first transport port used by this media.
*/
public int getPort() {
return mPort;
}
/**
* Returns the number of contiguous ports used by this media.
*/
public int getPortCount() {
return mPortCount;
}
/**
* Returns the transport protocol.
*/
public String getProtocol() {
return mProtocol;
}
/**
* Returns the media formats.
*/
public String[] getFormats() {
return mFormats.toArray(new String[mFormats.size()]);
}
/**
* Returns the {@code fmtp} attribute of the given format or
* {@code null} if it is not present.
*/
public String getFmtp(String format) {
return super.get("a=fmtp:" + format, ' ');
}
/**
* Sets a format and its {@code fmtp} attribute. If the attribute is
* {@code null}, the corresponding field will be removed.
*/
public void setFormat(String format, String fmtp) {
mFormats.remove(format);
mFormats.add(format);
super.set("a=rtpmap:" + format, ' ', null);
super.set("a=fmtp:" + format, ' ', fmtp);
}
/**
* Removes a format and its {@code fmtp} attribute.
*/
public void removeFormat(String format) {
mFormats.remove(format);
super.set("a=rtpmap:" + format, ' ', null);
super.set("a=fmtp:" + format, ' ', null);
}
/**
* Returns the RTP payload types.
*/
public int[] getRtpPayloadTypes() {
int[] types = new int[mFormats.size()];
int length = 0;
for (String format : mFormats) {
try {
types[length] = Integer.parseInt(format);
++length;
} catch (NumberFormatException e) { }
}
return Arrays.copyOf(types, length);
}
/**
* Returns the {@code rtpmap} attribute of the given RTP payload type
* or {@code null} if it is not present.
*/
public String getRtpmap(int type) {
return super.get("a=rtpmap:" + type, ' ');
}
/**
* Returns the {@code fmtp} attribute of the given RTP payload type or
* {@code null} if it is not present.
*/
public String getFmtp(int type) {
return super.get("a=fmtp:" + type, ' ');
}
/**
* Sets a RTP payload type and its {@code rtpmap} and {@code fmtp}
* attributes. If any of the attributes is {@code null}, the
* corresponding field will be removed. See
* {@link SimpleSessionDescription} for an example of its usage.
*/
public void setRtpPayload(int type, String rtpmap, String fmtp) {
String format = String.valueOf(type);
mFormats.remove(format);
mFormats.add(format);
super.set("a=rtpmap:" + format, ' ', rtpmap);
super.set("a=fmtp:" + format, ' ', fmtp);
}
/**
* Removes a RTP payload and its {@code rtpmap} and {@code fmtp}
* attributes.
*/
public void removeRtpPayload(int type) {
removeFormat(String.valueOf(type));
}
private void write(StringBuilder buffer) {
buffer.append("m=").append(mType).append(' ').append(mPort);
if (mPortCount != 1) {
buffer.append('/').append(mPortCount);
}
buffer.append(' ').append(mProtocol);
for (String format : mFormats) {
buffer.append(' ').append(format);
}
buffer.append("\r\n");
super.write(buffer);
}
}
/**
* This class acts as a set of fields, and the size of the set is expected
* to be small. Therefore, it uses a simple list instead of maps. Each field
* has three parts: a key, a delimiter, and a value. Delimiters are special
* because they are not included in binary attributes. As a result, the
* private methods, which are the building blocks of this class, all take
* the delimiter as an argument.
*/
private static class Fields {
private final String mOrder;
private final ArrayList<String> mLines = new ArrayList<String>();
Fields(String order) {
mOrder = order;
}
/**
* Returns the connection address or {@code null} if it is not present.
*/
public String getAddress() {
String address = get("c", '=');
if (address == null) {
return null;
}
String[] parts = address.split(" ");
if (parts.length != 3) {
return null;
}
int slash = parts[2].indexOf('/');
return (slash < 0) ? parts[2] : parts[2].substring(0, slash);
}
/**
* Sets the connection address. The field will be removed if the address
* is {@code null}.
*/
public void setAddress(String address) {
if (address != null) {
address = (address.indexOf(':') < 0 ? "IN IP4 " : "IN IP6 ") +
address;
}
set("c", '=', address);
}
/**
* Returns the encryption method or {@code null} if it is not present.
*/
public String getEncryptionMethod() {
String encryption = get("k", '=');
if (encryption == null) {
return null;
}
int colon = encryption.indexOf(':');
return (colon == -1) ? encryption : encryption.substring(0, colon);
}
/**
* Returns the encryption key or {@code null} if it is not present.
*/
public String getEncryptionKey() {
String encryption = get("k", '=');
if (encryption == null) {
return null;
}
int colon = encryption.indexOf(':');
return (colon == -1) ? null : encryption.substring(0, colon + 1);
}
/**
* Sets the encryption method and the encryption key. The field will be
* removed if the method is {@code null}.
*/
public void setEncryption(String method, String key) {
set("k", '=', (method == null || key == null) ?
method : method + ':' + key);
}
/**
* Returns the types of the bandwidth limits.
*/
public String[] getBandwidthTypes() {
return cut("b=", ':');
}
/**
* Returns the bandwidth limit of the given type or {@code -1} if it is
* not present.
*/
public int getBandwidth(String type) {
String value = get("b=" + type, ':');
if (value != null) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) { }
setBandwidth(type, -1);
}
return -1;
}
/**
* Sets the bandwith limit for the given type. The field will be removed
* if the value is negative.
*/
public void setBandwidth(String type, int value) {
set("b=" + type, ':', (value < 0) ? null : String.valueOf(value));
}
/**
* Returns the names of all the attributes.
*/
public String[] getAttributeNames() {
return cut("a=", ':');
}
/**
* Returns the attribute of the given name or {@code null} if it is not
* present.
*/
public String getAttribute(String name) {
return get("a=" + name, ':');
}
/**
* Sets the attribute for the given name. The field will be removed if
* the value is {@code null}. To set a binary attribute, use an empty
* string as the value.
*/
public void setAttribute(String name, String value) {
set("a=" + name, ':', value);
}
private void write(StringBuilder buffer) {
for (int i = 0; i < mOrder.length(); ++i) {
char type = mOrder.charAt(i);
for (String line : mLines) {
if (line.charAt(0) == type) {
buffer.append(line).append("\r\n");
}
}
}
}
/**
* Invokes {@link #set} after splitting the line into three parts.
*/
private void parse(String line) {
char type = line.charAt(0);
if (mOrder.indexOf(type) == -1) {
return;
}
char delimiter = '=';
if (line.startsWith("a=rtpmap:") || line.startsWith("a=fmtp:")) {
delimiter = ' ';
} else if (type == 'b' || type == 'a') {
delimiter = ':';
}
int i = line.indexOf(delimiter);
if (i == -1) {
set(line, delimiter, "");
} else {
set(line.substring(0, i), delimiter, line.substring(i + 1));
}
}
/**
* Finds the key with the given prefix and returns its suffix.
*/
private String[] cut(String prefix, char delimiter) {
String[] names = new String[mLines.size()];
int length = 0;
for (String line : mLines) {
if (line.startsWith(prefix)) {
int i = line.indexOf(delimiter);
if (i == -1) {
i = line.length();
}
names[length] = line.substring(prefix.length(), i);
++length;
}
}
return Arrays.copyOf(names, length);
}
/**
* Returns the index of the key.
*/
private int find(String key, char delimiter) {
int length = key.length();
for (int i = mLines.size() - 1; i >= 0; --i) {
String line = mLines.get(i);
if (line.startsWith(key) && (line.length() == length ||
line.charAt(length) == delimiter)) {
return i;
}
}
return -1;
}
/**
* Sets the key with the value or removes the key if the value is
* {@code null}.
*/
private void set(String key, char delimiter, String value) {
int index = find(key, delimiter);
if (value != null) {
if (value.length() != 0) {
key = key + delimiter + value;
}
if (index == -1) {
mLines.add(key);
} else {
mLines.set(index, key);
}
} else if (index != -1) {
mLines.remove(index);
}
}
/**
* Returns the value of the key.
*/
private String get(String key, char delimiter) {
int index = find(key, delimiter);
if (index == -1) {
return null;
}
String line = mLines.get(index);
int length = key.length();
return (line.length() == length) ? "" : line.substring(length + 1);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,101 +0,0 @@
/*
* 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 error codes returned during SIP actions. For example, during
* {@link SipRegistrationListener#onRegistrationFailed onRegistrationFailed()},
* {@link SipSession.Listener#onError onError()},
* {@link SipSession.Listener#onCallChangeFailed onCallChangeFailed()} and
* {@link SipSession.Listener#onRegistrationFailed onRegistrationFailed()}.
*/
public class SipErrorCode {
/** Not an error. */
public static final int NO_ERROR = 0;
/** When some socket error occurs. */
public static final int SOCKET_ERROR = -1;
/** When server responds with an error. */
public static final int SERVER_ERROR = -2;
/** When transaction is terminated unexpectedly. */
public static final int TRANSACTION_TERMINTED = -3;
/** When some error occurs on the device, possibly due to a bug. */
public static final int CLIENT_ERROR = -4;
/** When the transaction gets timed out. */
public static final int TIME_OUT = -5;
/** When the remote URI is not valid. */
public static final int INVALID_REMOTE_URI = -6;
/** When the peer is not reachable. */
public static final int PEER_NOT_REACHABLE = -7;
/** When invalid credentials are provided. */
public static final int INVALID_CREDENTIALS = -8;
/** The client is in a transaction and cannot initiate a new one. */
public static final int IN_PROGRESS = -9;
/** When data connection is lost. */
public static final int DATA_CONNECTION_LOST = -10;
/** Cross-domain authentication required. */
public static final int CROSS_DOMAIN_AUTHENTICATION = -11;
/** When the server is not reachable. */
public static final int SERVER_UNREACHABLE = -12;
public static String toString(int errorCode) {
switch (errorCode) {
case NO_ERROR:
return "NO_ERROR";
case SOCKET_ERROR:
return "SOCKET_ERROR";
case SERVER_ERROR:
return "SERVER_ERROR";
case TRANSACTION_TERMINTED:
return "TRANSACTION_TERMINTED";
case CLIENT_ERROR:
return "CLIENT_ERROR";
case TIME_OUT:
return "TIME_OUT";
case INVALID_REMOTE_URI:
return "INVALID_REMOTE_URI";
case PEER_NOT_REACHABLE:
return "PEER_NOT_REACHABLE";
case INVALID_CREDENTIALS:
return "INVALID_CREDENTIALS";
case IN_PROGRESS:
return "IN_PROGRESS";
case DATA_CONNECTION_LOST:
return "DATA_CONNECTION_LOST";
case CROSS_DOMAIN_AUTHENTICATION:
return "CROSS_DOMAIN_AUTHENTICATION";
case SERVER_UNREACHABLE:
return "SERVER_UNREACHABLE";
default:
return "UNKNOWN";
}
}
private SipErrorCode() {
}
}

View File

@@ -1,37 +0,0 @@
/*
* 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;
/**
* Indicates a general SIP-related exception.
*/
public class SipException extends Exception {
public SipException() {
}
public SipException(String message) {
super(message);
}
public SipException(String message, Throwable cause) {
// we want to eliminate the dependency on javax.sip.SipException
super(message, ((cause instanceof javax.sip.SipException)
&& (cause.getCause() != null))
? cause.getCause()
: cause);
}
}

View File

@@ -1,622 +0,0 @@
/*
* 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.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.telephony.Rlog;
import java.text.ParseException;
/**
* Provides APIs for SIP tasks, such as initiating SIP connections, and provides access to related
* SIP services. This class is the starting point for any SIP actions. You can acquire an instance
* of it with {@link #newInstance newInstance()}.</p>
* <p>The APIs in this class allows you to:</p>
* <ul>
* <li>Create a {@link SipSession} to get ready for making calls or listen for incoming calls. See
* {@link #createSipSession createSipSession()} and {@link #getSessionFor getSessionFor()}.</li>
* <li>Initiate and receive generic SIP calls or audio-only SIP calls. Generic SIP calls may
* be video, audio, or other, and are initiated with {@link #open open()}. Audio-only SIP calls
* should be handled with a {@link SipAudioCall}, which you can acquire with {@link
* #makeAudioCall makeAudioCall()} and {@link #takeAudioCall takeAudioCall()}.</li>
* <li>Register and unregister with a SIP service provider, with
* {@link #register register()} and {@link #unregister unregister()}.</li>
* <li>Verify session connectivity, with {@link #isOpened isOpened()} and
* {@link #isRegistered isRegistered()}.</li>
* </ul>
* <p class="note"><strong>Note:</strong> Not all Android-powered devices support VOIP calls using
* SIP. You should always call {@link android.net.sip.SipManager#isVoipSupported
* isVoipSupported()} to verify that the device supports VOIP calling and {@link
* android.net.sip.SipManager#isApiSupported isApiSupported()} to verify that the device supports
* the SIP APIs. Your application must also request the {@link
* android.Manifest.permission#INTERNET} and {@link android.Manifest.permission#USE_SIP}
* permissions.</p>
*
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For more information about using SIP, read the
* <a href="{@docRoot}guide/topics/network/sip.html">Session Initiation Protocol</a>
* developer guide.</p>
* </div>
*/
public class SipManager {
/**
* The result code to be sent back with the incoming call
* {@link PendingIntent}.
* @see #open(SipProfile, PendingIntent, SipRegistrationListener)
*/
public static final int INCOMING_CALL_RESULT_CODE = 101;
/**
* Key to retrieve the call ID from an incoming call intent.
* @see #open(SipProfile, PendingIntent, SipRegistrationListener)
*/
public static final String EXTRA_CALL_ID = "android:sipCallID";
/**
* Key to retrieve the offered session description from an incoming call
* intent.
* @see #open(SipProfile, PendingIntent, SipRegistrationListener)
*/
public static final String EXTRA_OFFER_SD = "android:sipOfferSD";
/**
* Action to broadcast when SipService is up.
* Internal use only.
* @hide
*/
public static final String ACTION_SIP_SERVICE_UP =
"android.net.sip.SIP_SERVICE_UP";
/**
* Action string for the incoming call intent for the Phone app.
* Internal use only.
* @hide
*/
public static final String ACTION_SIP_INCOMING_CALL =
"com.android.phone.SIP_INCOMING_CALL";
/**
* Action string for the add-phone intent.
* Internal use only.
* @hide
*/
public static final String ACTION_SIP_ADD_PHONE =
"com.android.phone.SIP_ADD_PHONE";
/**
* Action string for the remove-phone intent.
* Internal use only.
* @hide
*/
public static final String ACTION_SIP_REMOVE_PHONE =
"com.android.phone.SIP_REMOVE_PHONE";
/**
* Part of the ACTION_SIP_ADD_PHONE and ACTION_SIP_REMOVE_PHONE intents.
* Internal use only.
* @hide
*/
public static final String EXTRA_LOCAL_URI = "android:localSipUri";
private static final String TAG = "SipManager";
private ISipService mSipService;
private Context mContext;
/**
* Creates a manager instance. Returns null if SIP API is not supported.
*
* @param context application context for creating the manager object
* @return the manager instance or null if SIP API is not supported
*/
public static SipManager newInstance(Context context) {
return (isApiSupported(context) ? new SipManager(context) : null);
}
/**
* Returns true if the SIP API is supported by the system.
*/
public static boolean isApiSupported(Context context) {
return context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_SIP);
}
/**
* Returns true if the system supports SIP-based VOIP API.
*/
public static boolean isVoipSupported(Context context) {
return context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_SIP_VOIP) && isApiSupported(context);
}
/**
* Returns true if SIP is only available on WIFI.
*/
public static boolean isSipWifiOnly(Context context) {
return context.getResources().getBoolean(
com.android.internal.R.bool.config_sip_wifi_only);
}
private SipManager(Context context) {
mContext = context;
createSipService();
}
private void createSipService() {
IBinder b = ServiceManager.getService(Context.SIP_SERVICE);
mSipService = ISipService.Stub.asInterface(b);
}
/**
* Opens the profile for making generic SIP calls. The caller may make subsequent calls
* through {@link #makeAudioCall}. If one also wants to receive calls on the
* profile, use
* {@link #open(SipProfile, PendingIntent, SipRegistrationListener)}
* instead.
*
* @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 generic SIP calls. The caller may
* make subsequent calls through {@link #makeAudioCall}. If the
* auto-registration option is enabled in the profile, the SIP service
* will register the profile to the corresponding SIP provider periodically
* in order to receive calls from the provider. When the SIP service
* receives a new call, it will send out an intent with the provided action
* string. The intent contains a call ID extra and an offer session
* description string extra. Use {@link #getCallId} and
* {@link #getOfferSessionDescription} to retrieve those extras.
*
* @param localProfile the SIP profile to receive incoming calls for
* @param incomingCallPendingIntent When an incoming call is received, the
* SIP service will call
* {@link PendingIntent#send(Context, int, Intent)} to send back the
* intent to the caller with {@link #INCOMING_CALL_RESULT_CODE} as the
* result code and the intent to fill in the call ID and session
* description information. It cannot be null.
* @param listener to listen to registration events; can be null
* @see #getCallId
* @see #getOfferSessionDescription
* @see #takeAudioCall
* @throws NullPointerException if {@code incomingCallPendingIntent} is null
* @throws SipException if the profile contains incorrect settings or
* calling the SIP service results in an error
* @see #isIncomingCallIntent
* @see #getCallId
* @see #getOfferSessionDescription
*/
public void open(SipProfile localProfile,
PendingIntent incomingCallPendingIntent,
SipRegistrationListener listener) throws SipException {
if (incomingCallPendingIntent == null) {
throw new NullPointerException(
"incomingCallPendingIntent cannot be null");
}
try {
mSipService.open3(localProfile, incomingCallPendingIntent,
createRelay(listener, localProfile.getUriString()));
} 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, PendingIntent, SipRegistrationListener)}).
*
* @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, localProfileUri));
} 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 opened in the SIP service for
* making and/or receiving 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 SIP service has successfully registered the profile to the
* SIP provider (specified in the profile) for receiving calls. Returning
* true from this method also implies the profile is opened
* ({@link #isOpened}).
*
* @param localProfileUri the URI of the profile in question
* @return true if the profile is registered to the SIP provider; false if
* the profile has not been opened in the SIP service or the SIP
* service has not yet successfully registered the profile to the SIP
* provider
* @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. The attempt will be timed
* out if the call is not established within {@code timeout} seconds and
* {@link SipAudioCall.Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
* will be called.
*
* @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
* @param timeout the timeout value in seconds. Default value (defined by
* SIP protocol) is used if {@code timeout} is zero or negative.
* @return a {@link SipAudioCall} object
* @throws SipException if calling the SIP service results in an error or
* VOIP API is not supported by the device
* @see SipAudioCall.Listener#onError
* @see #isVoipSupported
*/
public SipAudioCall makeAudioCall(SipProfile localProfile,
SipProfile peerProfile, SipAudioCall.Listener listener, int timeout)
throws SipException {
if (!isVoipSupported(mContext)) {
throw new SipException("VOIP API is not supported");
}
SipAudioCall call = new SipAudioCall(mContext, localProfile);
call.setListener(listener);
SipSession s = createSipSession(localProfile, null);
call.makeCall(peerProfile, s, timeout);
return call;
}
/**
* Creates a {@link SipAudioCall} to make an audio call. The attempt will be
* timed out if the call is not established within {@code timeout} seconds
* and
* {@link SipAudioCall.Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
* will be called.
*
* @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
* @param timeout the timeout value in seconds. Default value (defined by
* SIP protocol) is used if {@code timeout} is zero or negative.
* @return a {@link SipAudioCall} object
* @throws SipException if calling the SIP service results in an error or
* VOIP API is not supported by the device
* @see SipAudioCall.Listener#onError
* @see #isVoipSupported
*/
public SipAudioCall makeAudioCall(String localProfileUri,
String peerProfileUri, SipAudioCall.Listener listener, int timeout)
throws SipException {
if (!isVoipSupported(mContext)) {
throw new SipException("VOIP API is not supported");
}
try {
return makeAudioCall(
new SipProfile.Builder(localProfileUri).build(),
new SipProfile.Builder(peerProfileUri).build(), listener,
timeout);
} catch (ParseException e) {
throw new SipException("build SipProfile", e);
}
}
/**
* Creates a {@link SipAudioCall} to take an incoming call. Before the call
* is returned, the listener will receive a
* {@link SipAudioCall.Listener#onRinging}
* callback.
*
* @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(Intent incomingCallIntent,
SipAudioCall.Listener listener) throws SipException {
if (incomingCallIntent == null) {
throw new SipException("Cannot retrieve session with null intent");
}
String callId = getCallId(incomingCallIntent);
if (callId == null) {
throw new SipException("Call ID missing in incoming call intent");
}
String offerSd = getOfferSessionDescription(incomingCallIntent);
if (offerSd == null) {
throw new SipException("Session description missing in incoming "
+ "call intent");
}
try {
ISipSession session = mSipService.getPendingSession(callId);
if (session == null) {
throw new SipException("No pending session for the call");
}
SipAudioCall call = new SipAudioCall(
mContext, session.getLocalProfile());
call.attachCall(new SipSession(session), offerSd);
call.setListener(listener);
return call;
} catch (Throwable t) {
throw new SipException("takeAudioCall()", t);
}
}
/**
* Checks if the intent is an incoming call broadcast intent.
*
* @param intent the intent in question
* @return true if the intent is an incoming call broadcast intent
*/
public static boolean isIncomingCallIntent(Intent intent) {
if (intent == null) return false;
String callId = getCallId(intent);
String offerSd = getOfferSessionDescription(intent);
return ((callId != null) && (offerSd != null));
}
/**
* Gets the call ID from the specified incoming call broadcast intent.
*
* @param incomingCallIntent the incoming call broadcast intent
* @return the call ID or null if the intent does not contain it
*/
public static String getCallId(Intent incomingCallIntent) {
return incomingCallIntent.getStringExtra(EXTRA_CALL_ID);
}
/**
* Gets the offer session description from the specified incoming call
* broadcast intent.
*
* @param incomingCallIntent the incoming call broadcast intent
* @return the offer session description or null if the intent does not
* have it
*/
public static String getOfferSessionDescription(Intent incomingCallIntent) {
return incomingCallIntent.getStringExtra(EXTRA_OFFER_SD);
}
/**
* Creates an incoming call broadcast intent.
*
* @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 callId,
String sessionDescription) {
Intent intent = new Intent();
intent.putExtra(EXTRA_CALL_ID, callId);
intent.putExtra(EXTRA_OFFER_SD, sessionDescription);
return intent;
}
/**
* Manually registers the profile to the corresponding SIP provider for
* receiving calls.
* {@link #open(SipProfile, PendingIntent, SipRegistrationListener)} is
* still needed to be called at least once in order for the SIP service to
* notify the caller with the {@link android.app.PendingIntent} when an incoming call is
* received.
*
* @param localProfile the SIP profile to register with
* @param expiryTime registration expiration time (in seconds)
* @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, localProfile.getUriString()));
if (session == null) {
throw new SipException(
"SipService.createSession() returns null");
}
session.register(expiryTime);
} catch (RemoteException e) {
throw new SipException("register()", e);
}
}
/**
* Manually unregisters the profile from the corresponding SIP provider for
* stop receiving further calls. This may interference with the auto
* registration process in the SIP service if the auto-registration option
* in the profile is enabled.
*
* @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, localProfile.getUriString()));
if (session == null) {
throw new SipException(
"SipService.createSession() returns null");
}
session.unregister();
} catch (RemoteException e) {
throw new SipException("unregister()", e);
}
}
/**
* Gets the {@link SipSession} that handles the incoming call. For audio
* calls, consider to use {@link SipAudioCall} to handle the incoming call.
* See {@link #takeAudioCall}. 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 SipSession getSessionFor(Intent incomingCallIntent)
throws SipException {
try {
String callId = getCallId(incomingCallIntent);
ISipSession s = mSipService.getPendingSession(callId);
return ((s == null) ? null : new SipSession(s));
} catch (RemoteException e) {
throw new SipException("getSessionFor()", e);
}
}
private static ISipSessionListener createRelay(
SipRegistrationListener listener, String uri) {
return ((listener == null) ? null : new ListenerRelay(listener, uri));
}
/**
* Creates a {@link SipSession} with the specified profile. Use other
* methods, if applicable, instead of interacting with {@link SipSession}
* directly.
*
* @param localProfile the SIP profile the session is associated with
* @param listener to listen to SIP session events
*/
public SipSession createSipSession(SipProfile localProfile,
SipSession.Listener listener) throws SipException {
try {
ISipSession s = mSipService.createSession(localProfile, null);
if (s == null) {
throw new SipException(
"Failed to create SipSession; network unavailable?");
}
return new SipSession(s, 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 new SipProfile[0];
}
}
private static class ListenerRelay extends SipSessionAdapter {
private SipRegistrationListener mListener;
private String mUri;
// listener must not be null
public ListenerRelay(SipRegistrationListener listener, String uri) {
mListener = listener;
mUri = uri;
}
private String getUri(ISipSession session) {
try {
return ((session == null)
? mUri
: session.getLocalProfile().getUriString());
} catch (Throwable e) {
// SipService died? SIP stack died?
Rlog.e(TAG, "getUri(): ", e);
return null;
}
}
@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, int errorCode,
String message) {
mListener.onRegistrationFailed(getUri(session), errorCode, message);
}
@Override
public void onRegistrationTimeout(ISipSession session) {
mListener.onRegistrationFailed(getUri(session),
SipErrorCode.TIME_OUT, "registration timed out");
}
}
}

View File

@@ -1,19 +0,0 @@
/*
* 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;

View File

@@ -1,502 +0,0 @@
/*
* 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.ObjectStreamException;
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;
/**
* Defines a SIP profile, including a SIP account, domain and server information.
* <p>You can create a {@link SipProfile} using {@link
* SipProfile.Builder}. You can also retrieve one from a {@link SipSession}, using {@link
* SipSession#getLocalProfile} and {@link SipSession#getPeerProfile}.</p>
*
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For more information about using SIP, read the
* <a href="{@docRoot}guide/topics/network/sip.html">Session Initiation Protocol</a>
* developer guide.</p>
* </div>
*/
public class SipProfile implements Parcelable, Serializable, Cloneable {
private static final long serialVersionUID = 1L;
private static final int DEFAULT_PORT = 5060;
private static final String TCP = "TCP";
private static final String UDP = "UDP";
private Address mAddress;
private String mProxyAddress;
private String mPassword;
private String mDomain;
private String mProtocol = UDP;
private String mProfileName;
private String mAuthUserName;
private int mPort = DEFAULT_PORT;
private boolean mSendKeepAlive = false;
private boolean mAutoRegistration = true;
private transient int mCallingUid = 0;
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];
}
};
/**
* Helper class for creating 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);
}
}
/**
* Creates a builder based on the given profile.
*/
public Builder(SipProfile profile) {
if (profile == null) throw new NullPointerException();
try {
mProfile = (SipProfile) profile.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException("should not occur", e);
}
mProfile.mAddress = null;
mUri = profile.getUri();
mUri.setUserPassword(profile.getPassword());
mDisplayName = profile.getDisplayName();
mProxyAddress = profile.getProxyAddress();
mProfile.mPort = profile.getPort();
}
/**
* 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} 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 username used for authentication.
*
* @param name authentication username of the profile
* @return this builder object
*/
public Builder setAuthUserName(String name) {
mProfile.mAuthUserName = name;
return this;
}
/**
* 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 IllegalArgumentException if the port number is out of range
*/
public Builder setPort(int port) throws IllegalArgumentException {
if ((port > 65535) || (port < 1000)) {
throw new IllegalArgumentException("incorrect port arugment: " + port);
}
mProfile.mPort = 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 IllegalArgumentException if the protocol is not recognized
*/
public Builder setProtocol(String protocol)
throws IllegalArgumentException {
if (protocol == null) {
throw new NullPointerException("protocol cannot be null");
}
protocol = protocol.toUpperCase();
if (!protocol.equals(UDP) && !protocol.equals(TCP)) {
throw new IllegalArgumentException(
"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 {
if (!TextUtils.isEmpty(mProxyAddress)) {
SipURI uri = (SipURI)
mAddressFactory.createURI(fix(mProxyAddress));
mProfile.mProxyAddress = uri.getHost();
} else {
if (!mProfile.mProtocol.equals(UDP)) {
mUri.setTransportParam(mProfile.mProtocol);
}
if (mProfile.mPort != DEFAULT_PORT) {
mUri.setPort(mProfile.mPort);
}
}
mProfile.mAddress = mAddressFactory.createAddress(
mDisplayName, mUri);
} catch (InvalidArgumentException e) {
throw new RuntimeException(e);
} 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;
mCallingUid = in.readInt();
mPort = in.readInt();
mAuthUserName = in.readString();
}
@Override
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);
out.writeInt(mCallingUid);
out.writeInt(mPort);
out.writeString(mAuthUserName);
}
@Override
public int describeContents() {
return 0;
}
/**
* Gets the SIP URI of this profile.
*
* @return the SIP URI of this profile
* @hide
*/
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() {
// We need to return the sip uri domain instead of
// the SIP URI with transport, port information if
// the outbound proxy address exists.
if (!TextUtils.isEmpty(mProxyAddress)) {
return "sip:" + getUserName() + "@" + mDomain;
}
return getUri().toString();
}
/**
* Gets the SIP address of this profile.
*
* @return the SIP address of this profile
* @hide
*/
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 username for authentication. If it is null, then the username
* is used in authentication instead.
*
* @return the authentication username
* @see #getUserName
*/
public String getAuthUserName() {
return mAuthUserName;
}
/**
* 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() {
return mPort;
}
/**
* 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;
}
/**
* Sets the calling process's Uid in the sip service.
* @hide
*/
public void setCallingUid(int uid) {
mCallingUid = uid;
}
/**
* Gets the calling process's Uid in the sip settings.
* @hide
*/
public int getCallingUid() {
return mCallingUid;
}
private Object readResolve() throws ObjectStreamException {
// For compatibility.
if (mPort == 0) mPort = DEFAULT_PORT;
return this;
}
}

View File

@@ -1,48 +0,0 @@
/*
* 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 for SIP registration events.
*/
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 the registration succeeded.
*
* @param localProfileUri the URI string of the SIP profile to register with
* @param expiryTime duration in seconds before the registration expires
*/
void onRegistrationDone(String localProfileUri, long expiryTime);
/**
* Called when the registration failed.
*
* @param localProfileUri the URI string of the SIP profile to register with
* @param errorCode error code of this error
* @param errorMessage error message
* @see SipErrorCode
*/
void onRegistrationFailed(String localProfileUri, int errorCode,
String errorMessage);
}

View File

@@ -1,574 +0,0 @@
/*
* 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.RemoteException;
import android.telephony.Rlog;
/**
* Represents a SIP session that is associated with a SIP dialog or a standalone
* transaction not within a dialog.
* <p>You can get a {@link SipSession} from {@link SipManager} with {@link
* SipManager#createSipSession createSipSession()} (when initiating calls) or {@link
* SipManager#getSessionFor getSessionFor()} (when receiving calls).</p>
*/
public final class SipSession {
private static final String TAG = "SipSession";
/**
* Defines SIP session states, such as "registering", "outgoing call", and "in call".
*/
public static class State {
/** When session is ready to initiate a call or transaction. */
public static final int READY_TO_CALL = 0;
/** When the registration request is sent out. */
public static final int REGISTERING = 1;
/** When the unregistration request is sent out. */
public static final int DEREGISTERING = 2;
/** When an INVITE request is received. */
public static final int INCOMING_CALL = 3;
/** When an OK response is sent for the INVITE request received. */
public static final int INCOMING_CALL_ANSWERING = 4;
/** When an INVITE request is sent. */
public static final int OUTGOING_CALL = 5;
/** When a RINGING response is received for the INVITE request sent. */
public static final int OUTGOING_CALL_RING_BACK = 6;
/** When a CANCEL request is sent for the INVITE request sent. */
public static final int OUTGOING_CALL_CANCELING = 7;
/** When a call is established. */
public static final int IN_CALL = 8;
/** When an OPTIONS request is sent. */
public static final int PINGING = 9;
/** When ending a call. @hide */
public static final int ENDING_CALL = 10;
/** Not defined. */
public static final int NOT_DEFINED = 101;
/**
* Converts the state to string.
*/
public static String toString(int state) {
switch (state) {
case READY_TO_CALL:
return "READY_TO_CALL";
case REGISTERING:
return "REGISTERING";
case DEREGISTERING:
return "DEREGISTERING";
case INCOMING_CALL:
return "INCOMING_CALL";
case INCOMING_CALL_ANSWERING:
return "INCOMING_CALL_ANSWERING";
case OUTGOING_CALL:
return "OUTGOING_CALL";
case OUTGOING_CALL_RING_BACK:
return "OUTGOING_CALL_RING_BACK";
case OUTGOING_CALL_CANCELING:
return "OUTGOING_CALL_CANCELING";
case IN_CALL:
return "IN_CALL";
case PINGING:
return "PINGING";
default:
return "NOT_DEFINED";
}
}
private State() {
}
}
/**
* Listener for events relating to a SIP session, such as when a session is being registered
* ("on registering") or a call is outgoing ("on calling").
* <p>Many of these events are also received by {@link SipAudioCall.Listener}.</p>
*/
public static class Listener {
/**
* Called when an INVITE request is sent to initiate a new call.
*
* @param session the session object that carries out the transaction
*/
public void onCalling(SipSession 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
*/
public void onRinging(SipSession session, SipProfile caller,
String sessionDescription) {
}
/**
* Called when a RINGING response is received for the INVITE request sent
*
* @param session the session object that carries out the transaction
*/
public void onRingingBack(SipSession 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
*/
public void onCallEstablished(SipSession session,
String sessionDescription) {
}
/**
* Called when the session is terminated.
*
* @param session the session object that is associated with the dialog
*/
public void onCallEnded(SipSession session) {
}
/**
* Called when the peer is busy during session initialization.
*
* @param session the session object that carries out the transaction
*/
public void onCallBusy(SipSession session) {
}
/**
* Called when the call is being transferred to a new one.
*
* @hide
* @param newSession the new session that the call will be transferred to
* @param sessionDescription the new peer's session description
*/
public void onCallTransferring(SipSession newSession,
String sessionDescription) {
}
/**
* Called when an error occurs during session initialization and
* termination.
*
* @param session the session object that carries out the transaction
* @param errorCode error code defined in {@link SipErrorCode}
* @param errorMessage error message
*/
public void onError(SipSession session, int errorCode,
String errorMessage) {
}
/**
* Called when an error occurs during session modification negotiation.
*
* @param session the session object that carries out the transaction
* @param errorCode error code defined in {@link SipErrorCode}
* @param errorMessage error message
*/
public void onCallChangeFailed(SipSession session, int errorCode,
String errorMessage) {
}
/**
* Called when a registration request is sent.
*
* @param session the session object that carries out the transaction
*/
public void onRegistering(SipSession 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
*/
public void onRegistrationDone(SipSession session, int duration) {
}
/**
* Called when the registration fails.
*
* @param session the session object that carries out the transaction
* @param errorCode error code defined in {@link SipErrorCode}
* @param errorMessage error message
*/
public void onRegistrationFailed(SipSession session, int errorCode,
String errorMessage) {
}
/**
* Called when the registration gets timed out.
*
* @param session the session object that carries out the transaction
*/
public void onRegistrationTimeout(SipSession session) {
}
}
private final ISipSession mSession;
private Listener mListener;
SipSession(ISipSession realSession) {
mSession = realSession;
if (realSession != null) {
try {
realSession.setListener(createListener());
} catch (RemoteException e) {
loge("SipSession.setListener:", e);
}
}
}
SipSession(ISipSession realSession, Listener listener) {
this(realSession);
setListener(listener);
}
/**
* Gets the IP address of the local host on which this SIP session runs.
*
* @return the IP address of the local host
*/
public String getLocalIp() {
try {
return mSession.getLocalIp();
} catch (RemoteException e) {
loge("getLocalIp:", e);
return "127.0.0.1";
}
}
/**
* Gets the SIP profile that this session is associated with.
*
* @return the SIP profile that this session is associated with
*/
public SipProfile getLocalProfile() {
try {
return mSession.getLocalProfile();
} catch (RemoteException e) {
loge("getLocalProfile:", e);
return null;
}
}
/**
* 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
*/
public SipProfile getPeerProfile() {
try {
return mSession.getPeerProfile();
} catch (RemoteException e) {
loge("getPeerProfile:", e);
return null;
}
}
/**
* Gets the session state. The value returned must be one of the states in
* {@link State}.
*
* @return the session state
*/
public int getState() {
try {
return mSession.getState();
} catch (RemoteException e) {
loge("getState:", e);
return State.NOT_DEFINED;
}
}
/**
* Checks if the session is in a call.
*
* @return true if the session is in a call
*/
public boolean isInCall() {
try {
return mSession.isInCall();
} catch (RemoteException e) {
loge("isInCall:", e);
return false;
}
}
/**
* Gets the call ID of the session.
*
* @return the call ID
*/
public String getCallId() {
try {
return mSession.getCallId();
} catch (RemoteException e) {
loge("getCallId:", e);
return null;
}
}
/**
* Sets the listener to listen to the session events. A {@code SipSession}
* 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
*/
public void setListener(Listener listener) {
mListener = 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 State#READY_TO_CALL}.
*
* @param duration duration in second before the registration expires
* @see Listener
*/
public void register(int duration) {
try {
mSession.register(duration);
} catch (RemoteException e) {
loge("register:", e);
}
}
/**
* 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 State#READY_TO_CALL}.
*
* @see Listener
*/
public void unregister() {
try {
mSession.unregister();
} catch (RemoteException e) {
loge("unregister:", e);
}
}
/**
* 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 State#READY_TO_CALL}.
*
* @param callee the SIP profile to make the call to
* @param sessionDescription the session description of this call
* @param timeout the session will be timed out if the call is not
* established within {@code timeout} seconds. Default value (defined
* by SIP protocol) is used if {@code timeout} is zero or negative.
* @see Listener
*/
public void makeCall(SipProfile callee, String sessionDescription,
int timeout) {
try {
mSession.makeCall(callee, sessionDescription, timeout);
} catch (RemoteException e) {
loge("makeCall:", e);
}
}
/**
* Answers an incoming call with the specified session description. The
* method is only valid to call when the session state is in
* {@link State#INCOMING_CALL}.
*
* @param sessionDescription the session description to answer this call
* @param timeout the session will be timed out if the call is not
* established within {@code timeout} seconds. Default value (defined
* by SIP protocol) is used if {@code timeout} is zero or negative.
*/
public void answerCall(String sessionDescription, int timeout) {
try {
mSession.answerCall(sessionDescription, timeout);
} catch (RemoteException e) {
loge("answerCall:", e);
}
}
/**
* 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 State#IN_CALL},
* {@link State#INCOMING_CALL},
* {@link State#OUTGOING_CALL} or
* {@link State#OUTGOING_CALL_RING_BACK}.
*/
public void endCall() {
try {
mSession.endCall();
} catch (RemoteException e) {
loge("endCall:", e);
}
}
/**
* Changes the session description during a call. The method is only valid
* to call when the session state is in {@link State#IN_CALL}.
*
* @param sessionDescription the new session description
* @param timeout the session will be timed out if the call is not
* established within {@code timeout} seconds. Default value (defined
* by SIP protocol) is used if {@code timeout} is zero or negative.
*/
public void changeCall(String sessionDescription, int timeout) {
try {
mSession.changeCall(sessionDescription, timeout);
} catch (RemoteException e) {
loge("changeCall:", e);
}
}
ISipSession getRealSession() {
return mSession;
}
private ISipSessionListener createListener() {
return new ISipSessionListener.Stub() {
@Override
public void onCalling(ISipSession session) {
if (mListener != null) {
mListener.onCalling(SipSession.this);
}
}
@Override
public void onRinging(ISipSession session, SipProfile caller,
String sessionDescription) {
if (mListener != null) {
mListener.onRinging(SipSession.this, caller,
sessionDescription);
}
}
@Override
public void onRingingBack(ISipSession session) {
if (mListener != null) {
mListener.onRingingBack(SipSession.this);
}
}
@Override
public void onCallEstablished(ISipSession session,
String sessionDescription) {
if (mListener != null) {
mListener.onCallEstablished(SipSession.this,
sessionDescription);
}
}
@Override
public void onCallEnded(ISipSession session) {
if (mListener != null) {
mListener.onCallEnded(SipSession.this);
}
}
@Override
public void onCallBusy(ISipSession session) {
if (mListener != null) {
mListener.onCallBusy(SipSession.this);
}
}
@Override
public void onCallTransferring(ISipSession session,
String sessionDescription) {
if (mListener != null) {
mListener.onCallTransferring(
new SipSession(session, SipSession.this.mListener),
sessionDescription);
}
}
@Override
public void onCallChangeFailed(ISipSession session, int errorCode,
String message) {
if (mListener != null) {
mListener.onCallChangeFailed(SipSession.this, errorCode,
message);
}
}
@Override
public void onError(ISipSession session, int errorCode, String message) {
if (mListener != null) {
mListener.onError(SipSession.this, errorCode, message);
}
}
@Override
public void onRegistering(ISipSession session) {
if (mListener != null) {
mListener.onRegistering(SipSession.this);
}
}
@Override
public void onRegistrationDone(ISipSession session, int duration) {
if (mListener != null) {
mListener.onRegistrationDone(SipSession.this, duration);
}
}
@Override
public void onRegistrationFailed(ISipSession session, int errorCode,
String message) {
if (mListener != null) {
mListener.onRegistrationFailed(SipSession.this, errorCode,
message);
}
}
@Override
public void onRegistrationTimeout(ISipSession session) {
if (mListener != null) {
mListener.onRegistrationTimeout(SipSession.this);
}
}
};
}
private void loge(String s, Throwable t) {
Rlog.e(TAG, s, t);
}
}

View File

@@ -1,68 +0,0 @@
/*
* 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,
String sessionDescription) {
}
public void onRingingBack(ISipSession session) {
}
public void onCallEstablished(ISipSession session,
String sessionDescription) {
}
public void onCallEnded(ISipSession session) {
}
public void onCallBusy(ISipSession session) {
}
public void onCallTransferring(ISipSession session,
String sessionDescription) {
}
public void onCallChangeFailed(ISipSession session, int errorCode,
String message) {
}
public void onError(ISipSession session, int errorCode, String message) {
}
public void onRegistering(ISipSession session) {
}
public void onRegistrationDone(ISipSession session, int duration) {
}
public void onRegistrationFailed(ISipSession session, int errorCode,
String message) {
}
public void onRegistrationTimeout(ISipSession session) {
}
}

View File

@@ -1,45 +0,0 @@
<HTML>
<BODY>
<p>Provides access to Session Initiation Protocol (SIP) functionality, such as
making and answering VOIP calls using SIP.</p>
<p>For more information, see the
<a href="{@docRoot}guide/topics/connectivity/sip.html">Session Initiation Protocol</a>
developer guide.</p>
{@more}
<p>To get started, you need to get an instance of the {@link android.net.sip.SipManager} by
calling {@link android.net.sip.SipManager#newInstance newInstance()}.</p>
<p>With the {@link android.net.sip.SipManager}, you can initiate SIP audio calls with {@link
android.net.sip.SipManager#makeAudioCall makeAudioCall()} and {@link
android.net.sip.SipManager#takeAudioCall takeAudioCall()}. Both methods require
a {@link android.net.sip.SipAudioCall.Listener} that receives callbacks when the state of the
call changes, such as when the call is ringing, established, or ended.</p>
<p>Both {@link android.net.sip.SipManager#makeAudioCall makeAudioCall()} also requires two
{@link android.net.sip.SipProfile} objects, representing the local device and the peer
device. You can create a {@link android.net.sip.SipProfile} using the {@link
android.net.sip.SipProfile.Builder} subclass.</p>
<p>Once you have a {@link android.net.sip.SipAudioCall}, you can perform SIP audio call actions with
the instance, such as make a call, answer a call, mute a call, turn on speaker mode, send DTMF
tones, and more.</p>
<p>If you want to create generic SIP connections (such as for video calls or other), you can
create a SIP connection from the {@link android.net.sip.SipManager}, using {@link
android.net.sip.SipManager#open open()}. If you only want to create audio SIP calls, though, you
should use the {@link android.net.sip.SipAudioCall} class, as described above.</p>
<p class="note"><strong>Note:</strong>
Not all Android-powered devices support VOIP functionality with SIP. Before performing any SIP
activity, you should call {@link android.net.sip.SipManager#isVoipSupported isVoipSupported()}
to verify that the device supports VOIP calling and {@link
android.net.sip.SipManager#isApiSupported isApiSupported()} to verify that the device supports the
SIP APIs.
Your application must also request the {@link android.Manifest.permission#INTERNET} and {@link
android.Manifest.permission#USE_SIP} permissions in order to use the SIP APIs.
</p>
</BODY>
</HTML>

View File

@@ -1,537 +0,0 @@
/*
* 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 com.android.server.sip;
import gov.nist.javax.sip.SipStackExt;
import gov.nist.javax.sip.clientauthutils.AccountManager;
import gov.nist.javax.sip.clientauthutils.AuthenticationHelper;
import gov.nist.javax.sip.header.extensions.ReferencesHeader;
import gov.nist.javax.sip.header.extensions.ReferredByHeader;
import gov.nist.javax.sip.header.extensions.ReplacesHeader;
import android.net.sip.SipProfile;
import android.telephony.Rlog;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.EventObject;
import java.util.List;
import java.util.regex.Pattern;
import javax.sip.ClientTransaction;
import javax.sip.Dialog;
import javax.sip.DialogTerminatedEvent;
import javax.sip.InvalidArgumentException;
import javax.sip.ListeningPoint;
import javax.sip.PeerUnavailableException;
import javax.sip.RequestEvent;
import javax.sip.ResponseEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.SipFactory;
import javax.sip.SipProvider;
import javax.sip.SipStack;
import javax.sip.Transaction;
import javax.sip.TransactionTerminatedEvent;
import javax.sip.TransactionState;
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.address.SipURI;
import javax.sip.header.CSeqHeader;
import javax.sip.header.CallIdHeader;
import javax.sip.header.ContactHeader;
import javax.sip.header.FromHeader;
import javax.sip.header.Header;
import javax.sip.header.HeaderFactory;
import javax.sip.header.MaxForwardsHeader;
import javax.sip.header.ToHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.Message;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.sip.message.Response;
/**
* Helper class for holding SIP stack related classes and for various low-level
* SIP tasks like sending messages.
*/
class SipHelper {
private static final String TAG = SipHelper.class.getSimpleName();
private static final boolean DBG = false;
private static final boolean DBG_PING = false;
private SipStack mSipStack;
private SipProvider mSipProvider;
private AddressFactory mAddressFactory;
private HeaderFactory mHeaderFactory;
private MessageFactory mMessageFactory;
public SipHelper(SipStack sipStack, SipProvider sipProvider)
throws PeerUnavailableException {
mSipStack = sipStack;
mSipProvider = sipProvider;
SipFactory sipFactory = SipFactory.getInstance();
mAddressFactory = sipFactory.createAddressFactory();
mHeaderFactory = sipFactory.createHeaderFactory();
mMessageFactory = sipFactory.createMessageFactory();
}
private FromHeader createFromHeader(SipProfile profile, String tag)
throws ParseException {
return mHeaderFactory.createFromHeader(profile.getSipAddress(), tag);
}
private ToHeader createToHeader(SipProfile profile) throws ParseException {
return createToHeader(profile, null);
}
private ToHeader createToHeader(SipProfile profile, String tag)
throws ParseException {
return mHeaderFactory.createToHeader(profile.getSipAddress(), tag);
}
private CallIdHeader createCallIdHeader() {
return mSipProvider.getNewCallId();
}
private CSeqHeader createCSeqHeader(String method)
throws ParseException, InvalidArgumentException {
long sequence = (long) (Math.random() * 10000);
return mHeaderFactory.createCSeqHeader(sequence, method);
}
private MaxForwardsHeader createMaxForwardsHeader()
throws InvalidArgumentException {
return mHeaderFactory.createMaxForwardsHeader(70);
}
private MaxForwardsHeader createMaxForwardsHeader(int max)
throws InvalidArgumentException {
return mHeaderFactory.createMaxForwardsHeader(max);
}
private ListeningPoint getListeningPoint() throws SipException {
ListeningPoint lp = mSipProvider.getListeningPoint(ListeningPoint.UDP);
if (lp == null) lp = mSipProvider.getListeningPoint(ListeningPoint.TCP);
if (lp == null) {
ListeningPoint[] lps = mSipProvider.getListeningPoints();
if ((lps != null) && (lps.length > 0)) lp = lps[0];
}
if (lp == null) {
throw new SipException("no listening point is available");
}
return lp;
}
private List<ViaHeader> createViaHeaders()
throws ParseException, SipException {
List<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(1);
ListeningPoint lp = getListeningPoint();
ViaHeader viaHeader = mHeaderFactory.createViaHeader(lp.getIPAddress(),
lp.getPort(), lp.getTransport(), null);
viaHeader.setRPort();
viaHeaders.add(viaHeader);
return viaHeaders;
}
private ContactHeader createContactHeader(SipProfile profile)
throws ParseException, SipException {
return createContactHeader(profile, null, 0);
}
private ContactHeader createContactHeader(SipProfile profile,
String ip, int port) throws ParseException,
SipException {
SipURI contactURI = (ip == null)
? createSipUri(profile.getUserName(), profile.getProtocol(),
getListeningPoint())
: createSipUri(profile.getUserName(), profile.getProtocol(),
ip, port);
Address contactAddress = mAddressFactory.createAddress(contactURI);
contactAddress.setDisplayName(profile.getDisplayName());
return mHeaderFactory.createContactHeader(contactAddress);
}
private ContactHeader createWildcardContactHeader() {
ContactHeader contactHeader = mHeaderFactory.createContactHeader();
contactHeader.setWildCard();
return contactHeader;
}
private SipURI createSipUri(String username, String transport,
ListeningPoint lp) throws ParseException {
return createSipUri(username, transport, lp.getIPAddress(), lp.getPort());
}
private SipURI createSipUri(String username, String transport,
String ip, int port) throws ParseException {
SipURI uri = mAddressFactory.createSipURI(username, ip);
try {
uri.setPort(port);
uri.setTransportParam(transport);
} catch (InvalidArgumentException e) {
throw new RuntimeException(e);
}
return uri;
}
public ClientTransaction sendOptions(SipProfile caller, SipProfile callee,
String tag) throws SipException {
try {
Request request = (caller == callee)
? createRequest(Request.OPTIONS, caller, tag)
: createRequest(Request.OPTIONS, caller, callee, tag);
ClientTransaction clientTransaction =
mSipProvider.getNewClientTransaction(request);
clientTransaction.sendRequest();
return clientTransaction;
} catch (Exception e) {
throw new SipException("sendOptions()", e);
}
}
public ClientTransaction sendRegister(SipProfile userProfile, String tag,
int expiry) throws SipException {
try {
Request request = createRequest(Request.REGISTER, userProfile, tag);
if (expiry == 0) {
// remove all previous registrations by wildcard
// rfc3261#section-10.2.2
request.addHeader(createWildcardContactHeader());
} else {
request.addHeader(createContactHeader(userProfile));
}
request.addHeader(mHeaderFactory.createExpiresHeader(expiry));
ClientTransaction clientTransaction =
mSipProvider.getNewClientTransaction(request);
clientTransaction.sendRequest();
return clientTransaction;
} catch (ParseException e) {
throw new SipException("sendRegister()", e);
}
}
private Request createRequest(String requestType, SipProfile userProfile,
String tag) throws ParseException, SipException {
FromHeader fromHeader = createFromHeader(userProfile, tag);
ToHeader toHeader = createToHeader(userProfile);
String replaceStr = Pattern.quote(userProfile.getUserName() + "@");
SipURI requestURI = mAddressFactory.createSipURI(
userProfile.getUriString().replaceFirst(replaceStr, ""));
List<ViaHeader> viaHeaders = createViaHeaders();
CallIdHeader callIdHeader = createCallIdHeader();
CSeqHeader cSeqHeader = createCSeqHeader(requestType);
MaxForwardsHeader maxForwards = createMaxForwardsHeader();
Request request = mMessageFactory.createRequest(requestURI,
requestType, callIdHeader, cSeqHeader, fromHeader,
toHeader, viaHeaders, maxForwards);
Header userAgentHeader = mHeaderFactory.createHeader("User-Agent",
"SIPAUA/0.1.001");
request.addHeader(userAgentHeader);
return request;
}
public ClientTransaction handleChallenge(ResponseEvent responseEvent,
AccountManager accountManager) throws SipException {
AuthenticationHelper authenticationHelper =
((SipStackExt) mSipStack).getAuthenticationHelper(
accountManager, mHeaderFactory);
ClientTransaction tid = responseEvent.getClientTransaction();
ClientTransaction ct = authenticationHelper.handleChallenge(
responseEvent.getResponse(), tid, mSipProvider, 5);
if (DBG) log("send request with challenge response: "
+ ct.getRequest());
ct.sendRequest();
return ct;
}
private Request createRequest(String requestType, SipProfile caller,
SipProfile callee, String tag) throws ParseException, SipException {
FromHeader fromHeader = createFromHeader(caller, tag);
ToHeader toHeader = createToHeader(callee);
SipURI requestURI = callee.getUri();
List<ViaHeader> viaHeaders = createViaHeaders();
CallIdHeader callIdHeader = createCallIdHeader();
CSeqHeader cSeqHeader = createCSeqHeader(requestType);
MaxForwardsHeader maxForwards = createMaxForwardsHeader();
Request request = mMessageFactory.createRequest(requestURI,
requestType, callIdHeader, cSeqHeader, fromHeader,
toHeader, viaHeaders, maxForwards);
request.addHeader(createContactHeader(caller));
return request;
}
public ClientTransaction sendInvite(SipProfile caller, SipProfile callee,
String sessionDescription, String tag, ReferredByHeader referredBy,
String replaces) throws SipException {
try {
Request request = createRequest(Request.INVITE, caller, callee, tag);
if (referredBy != null) request.addHeader(referredBy);
if (replaces != null) {
request.addHeader(mHeaderFactory.createHeader(
ReplacesHeader.NAME, replaces));
}
request.setContent(sessionDescription,
mHeaderFactory.createContentTypeHeader(
"application", "sdp"));
ClientTransaction clientTransaction =
mSipProvider.getNewClientTransaction(request);
if (DBG) log("send INVITE: " + request);
clientTransaction.sendRequest();
return clientTransaction;
} catch (ParseException e) {
throw new SipException("sendInvite()", e);
}
}
public ClientTransaction sendReinvite(Dialog dialog,
String sessionDescription) throws SipException {
try {
Request request = dialog.createRequest(Request.INVITE);
request.setContent(sessionDescription,
mHeaderFactory.createContentTypeHeader(
"application", "sdp"));
// Adding rport argument in the request could fix some SIP servers
// in resolving the initiator's NAT port mapping for relaying the
// response message from the other end.
ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
if (viaHeader != null) viaHeader.setRPort();
ClientTransaction clientTransaction =
mSipProvider.getNewClientTransaction(request);
if (DBG) log("send RE-INVITE: " + request);
dialog.sendRequest(clientTransaction);
return clientTransaction;
} catch (ParseException e) {
throw new SipException("sendReinvite()", e);
}
}
public ServerTransaction getServerTransaction(RequestEvent event)
throws SipException {
ServerTransaction transaction = event.getServerTransaction();
if (transaction == null) {
Request request = event.getRequest();
return mSipProvider.getNewServerTransaction(request);
} else {
return transaction;
}
}
/**
* @param event the INVITE request event
*/
public ServerTransaction sendRinging(RequestEvent event, String tag)
throws SipException {
try {
Request request = event.getRequest();
ServerTransaction transaction = getServerTransaction(event);
Response response = mMessageFactory.createResponse(Response.RINGING,
request);
ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME);
toHeader.setTag(tag);
response.addHeader(toHeader);
if (DBG) log("send RINGING: " + response);
transaction.sendResponse(response);
return transaction;
} catch (ParseException e) {
throw new SipException("sendRinging()", e);
}
}
/**
* @param event the INVITE request event
*/
public ServerTransaction sendInviteOk(RequestEvent event,
SipProfile localProfile, String sessionDescription,
ServerTransaction inviteTransaction, String externalIp,
int externalPort) throws SipException {
try {
Request request = event.getRequest();
Response response = mMessageFactory.createResponse(Response.OK,
request);
response.addHeader(createContactHeader(localProfile, externalIp,
externalPort));
response.setContent(sessionDescription,
mHeaderFactory.createContentTypeHeader(
"application", "sdp"));
if (inviteTransaction == null) {
inviteTransaction = getServerTransaction(event);
}
if (inviteTransaction.getState() != TransactionState.COMPLETED) {
if (DBG) log("send OK: " + response);
inviteTransaction.sendResponse(response);
}
return inviteTransaction;
} catch (ParseException e) {
throw new SipException("sendInviteOk()", e);
}
}
public void sendInviteBusyHere(RequestEvent event,
ServerTransaction inviteTransaction) throws SipException {
try {
Request request = event.getRequest();
Response response = mMessageFactory.createResponse(
Response.BUSY_HERE, request);
if (inviteTransaction == null) {
inviteTransaction = getServerTransaction(event);
}
if (inviteTransaction.getState() != TransactionState.COMPLETED) {
if (DBG) log("send BUSY HERE: " + response);
inviteTransaction.sendResponse(response);
}
} catch (ParseException e) {
throw new SipException("sendInviteBusyHere()", e);
}
}
/**
* @param event the INVITE ACK request event
*/
public void sendInviteAck(ResponseEvent event, Dialog dialog)
throws SipException {
Response response = event.getResponse();
long cseq = ((CSeqHeader) response.getHeader(CSeqHeader.NAME))
.getSeqNumber();
Request ack = dialog.createAck(cseq);
if (DBG) log("send ACK: " + ack);
dialog.sendAck(ack);
}
public void sendBye(Dialog dialog) throws SipException {
Request byeRequest = dialog.createRequest(Request.BYE);
if (DBG) log("send BYE: " + byeRequest);
dialog.sendRequest(mSipProvider.getNewClientTransaction(byeRequest));
}
public void sendCancel(ClientTransaction inviteTransaction)
throws SipException {
Request cancelRequest = inviteTransaction.createCancel();
if (DBG) log("send CANCEL: " + cancelRequest);
mSipProvider.getNewClientTransaction(cancelRequest).sendRequest();
}
public void sendResponse(RequestEvent event, int responseCode)
throws SipException {
try {
Request request = event.getRequest();
Response response = mMessageFactory.createResponse(
responseCode, request);
if (DBG && (!Request.OPTIONS.equals(request.getMethod())
|| DBG_PING)) {
log("send response: " + response);
}
getServerTransaction(event).sendResponse(response);
} catch (ParseException e) {
throw new SipException("sendResponse()", e);
}
}
public void sendReferNotify(Dialog dialog, String content)
throws SipException {
try {
Request request = dialog.createRequest(Request.NOTIFY);
request.addHeader(mHeaderFactory.createSubscriptionStateHeader(
"active;expires=60"));
// set content here
request.setContent(content,
mHeaderFactory.createContentTypeHeader(
"message", "sipfrag"));
request.addHeader(mHeaderFactory.createEventHeader(
ReferencesHeader.REFER));
if (DBG) log("send NOTIFY: " + request);
dialog.sendRequest(mSipProvider.getNewClientTransaction(request));
} catch (ParseException e) {
throw new SipException("sendReferNotify()", e);
}
}
public void sendInviteRequestTerminated(Request inviteRequest,
ServerTransaction inviteTransaction) throws SipException {
try {
Response response = mMessageFactory.createResponse(
Response.REQUEST_TERMINATED, inviteRequest);
if (DBG) log("send response: " + response);
inviteTransaction.sendResponse(response);
} catch (ParseException e) {
throw new SipException("sendInviteRequestTerminated()", e);
}
}
public static String getCallId(EventObject event) {
if (event == null) return null;
if (event instanceof RequestEvent) {
return getCallId(((RequestEvent) event).getRequest());
} else if (event instanceof ResponseEvent) {
return getCallId(((ResponseEvent) event).getResponse());
} else if (event instanceof DialogTerminatedEvent) {
Dialog dialog = ((DialogTerminatedEvent) event).getDialog();
return getCallId(((DialogTerminatedEvent) event).getDialog());
} else if (event instanceof TransactionTerminatedEvent) {
TransactionTerminatedEvent e = (TransactionTerminatedEvent) event;
return getCallId(e.isServerTransaction()
? e.getServerTransaction()
: e.getClientTransaction());
} else {
Object source = event.getSource();
if (source instanceof Transaction) {
return getCallId(((Transaction) source));
} else if (source instanceof Dialog) {
return getCallId((Dialog) source);
}
}
return "";
}
public static String getCallId(Transaction transaction) {
return ((transaction != null) ? getCallId(transaction.getRequest())
: "");
}
private static String getCallId(Message message) {
CallIdHeader callIdHeader =
(CallIdHeader) message.getHeader(CallIdHeader.NAME);
return callIdHeader.getCallId();
}
private static String getCallId(Dialog dialog) {
return dialog.getCallId().getCallId();
}
private void log(String s) {
Rlog.d(TAG, s);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,265 +0,0 @@
/*
* 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 com.android.server.sip;
import android.net.sip.ISipSession;
import android.net.sip.ISipSessionListener;
import android.net.sip.SipProfile;
import android.os.DeadObjectException;
import android.telephony.Rlog;
/** Class to help safely run a callback in a different thread. */
class SipSessionListenerProxy extends ISipSessionListener.Stub {
private static final String TAG = "SipSessionListnerProxy";
private ISipSessionListener mListener;
public void setListener(ISipSessionListener listener) {
mListener = listener;
}
public ISipSessionListener getListener() {
return mListener;
}
private void proxy(Runnable runnable) {
// One thread for each calling back.
// Note: Guarantee ordering if the issue becomes important. Currently,
// the chance of handling two callback events at a time is none.
new Thread(runnable, "SipSessionCallbackThread").start();
}
@Override
public void onCalling(final ISipSession session) {
if (mListener == null) return;
proxy(new Runnable() {
@Override
public void run() {
try {
mListener.onCalling(session);
} catch (Throwable t) {
handle(t, "onCalling()");
}
}
});
}
@Override
public void onRinging(final ISipSession session, final SipProfile caller,
final String sessionDescription) {
if (mListener == null) return;
proxy(new Runnable() {
@Override
public void run() {
try {
mListener.onRinging(session, caller, sessionDescription);
} catch (Throwable t) {
handle(t, "onRinging()");
}
}
});
}
@Override
public void onRingingBack(final ISipSession session) {
if (mListener == null) return;
proxy(new Runnable() {
@Override
public void run() {
try {
mListener.onRingingBack(session);
} catch (Throwable t) {
handle(t, "onRingingBack()");
}
}
});
}
@Override
public void onCallEstablished(final ISipSession session,
final String sessionDescription) {
if (mListener == null) return;
proxy(new Runnable() {
@Override
public void run() {
try {
mListener.onCallEstablished(session, sessionDescription);
} catch (Throwable t) {
handle(t, "onCallEstablished()");
}
}
});
}
@Override
public void onCallEnded(final ISipSession session) {
if (mListener == null) return;
proxy(new Runnable() {
@Override
public void run() {
try {
mListener.onCallEnded(session);
} catch (Throwable t) {
handle(t, "onCallEnded()");
}
}
});
}
@Override
public void onCallTransferring(final ISipSession newSession,
final String sessionDescription) {
if (mListener == null) return;
proxy(new Runnable() {
@Override
public void run() {
try {
mListener.onCallTransferring(newSession, sessionDescription);
} catch (Throwable t) {
handle(t, "onCallTransferring()");
}
}
});
}
@Override
public void onCallBusy(final ISipSession session) {
if (mListener == null) return;
proxy(new Runnable() {
@Override
public void run() {
try {
mListener.onCallBusy(session);
} catch (Throwable t) {
handle(t, "onCallBusy()");
}
}
});
}
@Override
public void onCallChangeFailed(final ISipSession session,
final int errorCode, final String message) {
if (mListener == null) return;
proxy(new Runnable() {
@Override
public void run() {
try {
mListener.onCallChangeFailed(session, errorCode, message);
} catch (Throwable t) {
handle(t, "onCallChangeFailed()");
}
}
});
}
@Override
public void onError(final ISipSession session, final int errorCode,
final String message) {
if (mListener == null) return;
proxy(new Runnable() {
@Override
public void run() {
try {
mListener.onError(session, errorCode, message);
} catch (Throwable t) {
handle(t, "onError()");
}
}
});
}
@Override
public void onRegistering(final ISipSession session) {
if (mListener == null) return;
proxy(new Runnable() {
@Override
public void run() {
try {
mListener.onRegistering(session);
} catch (Throwable t) {
handle(t, "onRegistering()");
}
}
});
}
@Override
public void onRegistrationDone(final ISipSession session,
final int duration) {
if (mListener == null) return;
proxy(new Runnable() {
@Override
public void run() {
try {
mListener.onRegistrationDone(session, duration);
} catch (Throwable t) {
handle(t, "onRegistrationDone()");
}
}
});
}
@Override
public void onRegistrationFailed(final ISipSession session,
final int errorCode, final String message) {
if (mListener == null) return;
proxy(new Runnable() {
@Override
public void run() {
try {
mListener.onRegistrationFailed(session, errorCode, message);
} catch (Throwable t) {
handle(t, "onRegistrationFailed()");
}
}
});
}
@Override
public void onRegistrationTimeout(final ISipSession session) {
if (mListener == null) return;
proxy(new Runnable() {
@Override
public void run() {
try {
mListener.onRegistrationTimeout(session);
} catch (Throwable t) {
handle(t, "onRegistrationTimeout()");
}
}
});
}
private void handle(Throwable t, String message) {
if (t instanceof DeadObjectException) {
mListener = null;
// This creates race but it's harmless. Just don't log the error
// when it happens.
} else if (mListener != null) {
loge(message, t);
}
}
private void log(String s) {
Rlog.d(TAG, s);
}
private void loge(String s, Throwable t) {
Rlog.e(TAG, s, t);
}
}

View File

@@ -1,73 +0,0 @@
/*
* 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 com.android.server.sip;
import android.os.PowerManager;
import android.telephony.Rlog;
import java.util.HashSet;
class SipWakeLock {
private static final String TAG = "SipWakeLock";
private static final boolean DBG = false;
private PowerManager mPowerManager;
private PowerManager.WakeLock mWakeLock;
private PowerManager.WakeLock mTimerWakeLock;
private HashSet<Object> mHolders = new HashSet<Object>();
SipWakeLock(PowerManager powerManager) {
mPowerManager = powerManager;
}
synchronized void reset() {
if (DBG) log("reset count=" + mHolders.size());
mHolders.clear();
release(null);
}
synchronized void acquire(long timeout) {
if (mTimerWakeLock == null) {
mTimerWakeLock = mPowerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "SipWakeLock.timer");
mTimerWakeLock.setReferenceCounted(true);
}
mTimerWakeLock.acquire(timeout);
}
synchronized void acquire(Object holder) {
mHolders.add(holder);
if (mWakeLock == null) {
mWakeLock = mPowerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "SipWakeLock");
}
if (!mWakeLock.isHeld()) mWakeLock.acquire();
if (DBG) log("acquire count=" + mHolders.size());
}
synchronized void release(Object holder) {
mHolders.remove(holder);
if ((mWakeLock != null) && mHolders.isEmpty()
&& mWakeLock.isHeld()) {
mWakeLock.release();
}
if (DBG) log("release count=" + mHolders.size());
}
private void log(String s) {
Rlog.d(TAG, s);
}
}

View File

@@ -1,336 +0,0 @@
/*
* Copyright (C) 2011, 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 com.android.server.sip;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.SystemClock;
import android.telephony.Rlog;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
import java.util.concurrent.Executor;
/**
* Timer that can schedule events to occur even when the device is in sleep.
*/
class SipWakeupTimer extends BroadcastReceiver {
private static final String TAG = "SipWakeupTimer";
private static final boolean DBG = SipService.DBG && true; // STOPSHIP if true
private static final String TRIGGER_TIME = "TriggerTime";
private Context mContext;
private AlarmManager mAlarmManager;
// runnable --> time to execute in SystemClock
private TreeSet<MyEvent> mEventQueue =
new TreeSet<MyEvent>(new MyEventComparator());
private PendingIntent mPendingIntent;
private Executor mExecutor;
public SipWakeupTimer(Context context, Executor executor) {
mContext = context;
mAlarmManager = (AlarmManager)
context.getSystemService(Context.ALARM_SERVICE);
IntentFilter filter = new IntentFilter(getAction());
context.registerReceiver(this, filter);
mExecutor = executor;
}
/**
* Stops the timer. No event can be scheduled after this method is called.
*/
public synchronized void stop() {
mContext.unregisterReceiver(this);
if (mPendingIntent != null) {
mAlarmManager.cancel(mPendingIntent);
mPendingIntent = null;
}
mEventQueue.clear();
mEventQueue = null;
}
private boolean stopped() {
if (mEventQueue == null) {
if (DBG) log("Timer stopped");
return true;
} else {
return false;
}
}
private void cancelAlarm() {
mAlarmManager.cancel(mPendingIntent);
mPendingIntent = null;
}
private void recalculatePeriods() {
if (mEventQueue.isEmpty()) return;
MyEvent firstEvent = mEventQueue.first();
int minPeriod = firstEvent.mMaxPeriod;
long minTriggerTime = firstEvent.mTriggerTime;
for (MyEvent e : mEventQueue) {
e.mPeriod = e.mMaxPeriod / minPeriod * minPeriod;
int interval = (int) (e.mLastTriggerTime + e.mMaxPeriod
- minTriggerTime);
interval = interval / minPeriod * minPeriod;
e.mTriggerTime = minTriggerTime + interval;
}
TreeSet<MyEvent> newQueue = new TreeSet<MyEvent>(
mEventQueue.comparator());
newQueue.addAll(mEventQueue);
mEventQueue.clear();
mEventQueue = newQueue;
if (DBG) {
log("queue re-calculated");
printQueue();
}
}
// Determines the period and the trigger time of the new event and insert it
// to the queue.
private void insertEvent(MyEvent event) {
long now = SystemClock.elapsedRealtime();
if (mEventQueue.isEmpty()) {
event.mTriggerTime = now + event.mPeriod;
mEventQueue.add(event);
return;
}
MyEvent firstEvent = mEventQueue.first();
int minPeriod = firstEvent.mPeriod;
if (minPeriod <= event.mMaxPeriod) {
event.mPeriod = event.mMaxPeriod / minPeriod * minPeriod;
int interval = event.mMaxPeriod;
interval -= (int) (firstEvent.mTriggerTime - now);
interval = interval / minPeriod * minPeriod;
event.mTriggerTime = firstEvent.mTriggerTime + interval;
mEventQueue.add(event);
} else {
long triggerTime = now + event.mPeriod;
if (firstEvent.mTriggerTime < triggerTime) {
event.mTriggerTime = firstEvent.mTriggerTime;
event.mLastTriggerTime -= event.mPeriod;
} else {
event.mTriggerTime = triggerTime;
}
mEventQueue.add(event);
recalculatePeriods();
}
}
/**
* Sets a periodic timer.
*
* @param period the timer period; in milli-second
* @param callback is called back when the timer goes off; the same callback
* can be specified in multiple timer events
*/
public synchronized void set(int period, Runnable callback) {
if (stopped()) return;
long now = SystemClock.elapsedRealtime();
MyEvent event = new MyEvent(period, callback, now);
insertEvent(event);
if (mEventQueue.first() == event) {
if (mEventQueue.size() > 1) cancelAlarm();
scheduleNext();
}
long triggerTime = event.mTriggerTime;
if (DBG) {
log("set: add event " + event + " scheduled on "
+ showTime(triggerTime) + " at " + showTime(now)
+ ", #events=" + mEventQueue.size());
printQueue();
}
}
/**
* Cancels all the timer events with the specified callback.
*
* @param callback the callback
*/
public synchronized void cancel(Runnable callback) {
if (stopped() || mEventQueue.isEmpty()) return;
if (DBG) log("cancel:" + callback);
MyEvent firstEvent = mEventQueue.first();
for (Iterator<MyEvent> iter = mEventQueue.iterator();
iter.hasNext();) {
MyEvent event = iter.next();
if (event.mCallback == callback) {
iter.remove();
if (DBG) log(" cancel found:" + event);
}
}
if (mEventQueue.isEmpty()) {
cancelAlarm();
} else if (mEventQueue.first() != firstEvent) {
cancelAlarm();
firstEvent = mEventQueue.first();
firstEvent.mPeriod = firstEvent.mMaxPeriod;
firstEvent.mTriggerTime = firstEvent.mLastTriggerTime
+ firstEvent.mPeriod;
recalculatePeriods();
scheduleNext();
}
if (DBG) {
log("cancel: X");
printQueue();
}
}
private void scheduleNext() {
if (stopped() || mEventQueue.isEmpty()) return;
if (mPendingIntent != null) {
throw new RuntimeException("pendingIntent is not null!");
}
MyEvent event = mEventQueue.first();
Intent intent = new Intent(getAction());
intent.putExtra(TRIGGER_TIME, event.mTriggerTime);
PendingIntent pendingIntent = mPendingIntent =
PendingIntent.getBroadcast(mContext, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
event.mTriggerTime, pendingIntent);
}
@Override
public synchronized void onReceive(Context context, Intent intent) {
// This callback is already protected by AlarmManager's wake lock.
String action = intent.getAction();
if (getAction().equals(action)
&& intent.getExtras().containsKey(TRIGGER_TIME)) {
mPendingIntent = null;
long triggerTime = intent.getLongExtra(TRIGGER_TIME, -1L);
execute(triggerTime);
} else {
log("onReceive: unrecognized intent: " + intent);
}
}
private void printQueue() {
int count = 0;
for (MyEvent event : mEventQueue) {
log(" " + event + ": scheduled at "
+ showTime(event.mTriggerTime) + ": last at "
+ showTime(event.mLastTriggerTime));
if (++count >= 5) break;
}
if (mEventQueue.size() > count) {
log(" .....");
} else if (count == 0) {
log(" <empty>");
}
}
private void execute(long triggerTime) {
if (DBG) log("time's up, triggerTime = "
+ showTime(triggerTime) + ": " + mEventQueue.size());
if (stopped() || mEventQueue.isEmpty()) return;
for (MyEvent event : mEventQueue) {
if (event.mTriggerTime != triggerTime) continue;
if (DBG) log("execute " + event);
event.mLastTriggerTime = triggerTime;
event.mTriggerTime += event.mPeriod;
// run the callback in the handler thread to prevent deadlock
mExecutor.execute(event.mCallback);
}
if (DBG) {
log("after timeout execution");
printQueue();
}
scheduleNext();
}
private String getAction() {
return toString();
}
private String showTime(long time) {
int ms = (int) (time % 1000);
int s = (int) (time / 1000);
int m = s / 60;
s %= 60;
return String.format("%d.%d.%d", m, s, ms);
}
private static class MyEvent {
int mPeriod;
int mMaxPeriod;
long mTriggerTime;
long mLastTriggerTime;
Runnable mCallback;
MyEvent(int period, Runnable callback, long now) {
mPeriod = mMaxPeriod = period;
mCallback = callback;
mLastTriggerTime = now;
}
@Override
public String toString() {
String s = super.toString();
s = s.substring(s.indexOf("@"));
return s + ":" + (mPeriod / 1000) + ":" + (mMaxPeriod / 1000) + ":"
+ toString(mCallback);
}
private String toString(Object o) {
String s = o.toString();
int index = s.indexOf("$");
if (index > 0) s = s.substring(index + 1);
return s;
}
}
// Sort the events by mMaxPeriod so that the first event can be used to
// align events with larger periods
private static class MyEventComparator implements Comparator<MyEvent> {
@Override
public int compare(MyEvent e1, MyEvent e2) {
if (e1 == e2) return 0;
int diff = e1.mMaxPeriod - e2.mMaxPeriod;
if (diff == 0) diff = -1;
return diff;
}
@Override
public boolean equals(Object that) {
return (this == that);
}
}
private void log(String s) {
Rlog.d(TAG, s);
}
}

View File

@@ -1,272 +0,0 @@
/*
* Copyrightm (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.
*/
#include <string.h>
#include "AudioCodec.h"
#include "gsmamr_dec.h"
#include "gsmamr_enc.h"
namespace {
const int gFrameBits[8] = {95, 103, 118, 134, 148, 159, 204, 244};
//------------------------------------------------------------------------------
// See RFC 4867 for the encoding details.
class AmrCodec : public AudioCodec
{
public:
AmrCodec() {
if (AMREncodeInit(&mEncoder, &mSidSync, false)) {
mEncoder = NULL;
}
if (GSMInitDecode(&mDecoder, (Word8 *)"RTP")) {
mDecoder = NULL;
}
}
~AmrCodec() {
if (mEncoder) {
AMREncodeExit(&mEncoder, &mSidSync);
}
if (mDecoder) {
GSMDecodeFrameExit(&mDecoder);
}
}
int set(int sampleRate, const char *fmtp);
int encode(void *payload, int16_t *samples);
int decode(int16_t *samples, int count, void *payload, int length);
private:
void *mEncoder;
void *mSidSync;
void *mDecoder;
int mMode;
int mModeSet;
bool mOctetAligned;
};
int AmrCodec::set(int sampleRate, const char *fmtp)
{
// These parameters are not supported.
if (strcasestr(fmtp, "crc=1") || strcasestr(fmtp, "robust-sorting=1") ||
strcasestr(fmtp, "interleaving=")) {
return -1;
}
// Handle mode-set and octet-align.
const char *modes = strcasestr(fmtp, "mode-set=");
if (modes) {
mMode = 0;
mModeSet = 0;
for (char c = *modes; c && c != ' '; c = *++modes) {
if (c >= '0' && c <= '7') {
int mode = c - '0';
if (mode > mMode) {
mMode = mode;
}
mModeSet |= 1 << mode;
}
}
} else {
mMode = 7;
mModeSet = 0xFF;
}
mOctetAligned = (strcasestr(fmtp, "octet-align=1") != NULL);
// TODO: handle mode-change-*.
return (sampleRate == 8000 && mEncoder && mDecoder) ? 160 : -1;
}
int AmrCodec::encode(void *payload, int16_t *samples)
{
unsigned char *bytes = (unsigned char *)payload;
Frame_Type_3GPP type;
int length = AMREncode(mEncoder, mSidSync, (Mode)mMode,
samples, bytes + 1, &type, AMR_TX_WMF);
if (type != mMode || length != (8 + gFrameBits[mMode] + 7) >> 3) {
return -1;
}
if (mOctetAligned) {
bytes[0] = 0xF0;
bytes[1] = (mMode << 3) | 0x04;
++length;
} else {
// CMR = 15 (4-bit), F = 0 (1-bit), FT = mMode (4-bit), Q = 1 (1-bit).
bytes[0] = 0xFF;
bytes[1] = 0xC0 | (mMode << 1) | 1;
// Shift left 6 bits and update the length.
bytes[length + 1] = 0;
for (int i = 0; i <= length; ++i) {
bytes[i] = (bytes[i] << 6) | (bytes[i + 1] >> 2);
}
length = (10 + gFrameBits[mMode] + 7) >> 3;
}
return length;
}
int AmrCodec::decode(int16_t *samples, int count, void *payload, int length)
{
unsigned char *bytes = (unsigned char *)payload;
Frame_Type_3GPP type;
if (length < 2) {
return -1;
}
int request = bytes[0] >> 4;
if (mOctetAligned) {
if ((bytes[1] & 0xC4) != 0x04) {
return -1;
}
type = (Frame_Type_3GPP)(bytes[1] >> 3);
if (length != (16 + gFrameBits[type] + 7) >> 3) {
return -1;
}
length -= 2;
bytes += 2;
} else {
if ((bytes[0] & 0x0C) || !(bytes[1] & 0x40)) {
return -1;
}
type = (Frame_Type_3GPP)((bytes[0] << 1 | bytes[1] >> 7) & 0x07);
if (length != (10 + gFrameBits[type] + 7) >> 3) {
return -1;
}
// Shift left 2 bits and update the length.
--length;
for (int i = 1; i < length; ++i) {
bytes[i] = (bytes[i] << 2) | (bytes[i + 1] >> 6);
}
bytes[length] <<= 2;
length = (gFrameBits[type] + 7) >> 3;
++bytes;
}
if (AMRDecode(mDecoder, type, bytes, samples, MIME_IETF) != length) {
return -1;
}
// Handle CMR
if (request < 8 && request != mMode) {
for (int i = request; i >= 0; --i) {
if (mModeSet & (1 << i)) {
mMode = request;
break;
}
}
}
return 160;
}
//------------------------------------------------------------------------------
// See RFC 3551 for the encoding details.
class GsmEfrCodec : public AudioCodec
{
public:
GsmEfrCodec() {
if (AMREncodeInit(&mEncoder, &mSidSync, false)) {
mEncoder = NULL;
}
if (GSMInitDecode(&mDecoder, (Word8 *)"RTP")) {
mDecoder = NULL;
}
}
~GsmEfrCodec() {
if (mEncoder) {
AMREncodeExit(&mEncoder, &mSidSync);
}
if (mDecoder) {
GSMDecodeFrameExit(&mDecoder);
}
}
int set(int sampleRate, const char *fmtp) {
return (sampleRate == 8000 && mEncoder && mDecoder) ? 160 : -1;
}
int encode(void *payload, int16_t *samples);
int decode(int16_t *samples, int count, void *payload, int length);
private:
void *mEncoder;
void *mSidSync;
void *mDecoder;
};
int GsmEfrCodec::encode(void *payload, int16_t *samples)
{
unsigned char *bytes = (unsigned char *)payload;
Frame_Type_3GPP type;
int length = AMREncode(mEncoder, mSidSync, MR122,
samples, bytes, &type, AMR_TX_WMF);
if (type == AMR_122 && length == 32) {
bytes[0] = 0xC0 | (bytes[1] >> 4);
for (int i = 1; i < 31; ++i) {
bytes[i] = (bytes[i] << 4) | (bytes[i + 1] >> 4);
}
return 31;
}
return -1;
}
int GsmEfrCodec::decode(int16_t *samples, int count, void *payload, int length)
{
unsigned char *bytes = (unsigned char *)payload;
int n = 0;
while (n + 160 <= count && length >= 31 && (bytes[0] >> 4) == 0x0C) {
for (int i = 0; i < 30; ++i) {
bytes[i] = (bytes[i] << 4) | (bytes[i + 1] >> 4);
}
bytes[30] <<= 4;
if (AMRDecode(mDecoder, AMR_122, bytes, &samples[n], MIME_IETF) != 31) {
break;
}
n += 160;
length -= 31;
bytes += 31;
}
return n;
}
} // namespace
AudioCodec *newAmrCodec()
{
return new AmrCodec;
}
AudioCodec *newGsmEfrCodec()
{
return new GsmEfrCodec;
}

View File

@@ -1,59 +0,0 @@
#
# 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.
#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := librtp_jni
LOCAL_SRC_FILES := \
AudioCodec.cpp \
AudioGroup.cpp \
EchoSuppressor.cpp \
RtpStream.cpp \
util.cpp \
rtp_jni.cpp
LOCAL_SRC_FILES += \
AmrCodec.cpp \
G711Codec.cpp \
GsmCodec.cpp
LOCAL_SHARED_LIBRARIES := \
libnativehelper \
libcutils \
libutils \
libmedia \
libstagefright_amrnb_common
LOCAL_STATIC_LIBRARIES := libgsm libstagefright_amrnbdec libstagefright_amrnbenc
LOCAL_C_INCLUDES += \
$(JNI_H_INCLUDE) \
external/libgsm/inc \
frameworks/av/media/libstagefright/codecs/amrnb/common/include \
frameworks/av/media/libstagefright/codecs/amrnb/common/ \
frameworks/av/media/libstagefright/codecs/amrnb/enc/include \
frameworks/av/media/libstagefright/codecs/amrnb/enc/src \
frameworks/av/media/libstagefright/codecs/amrnb/dec/include \
frameworks/av/media/libstagefright/codecs/amrnb/dec/src \
$(call include-path-for, audio-effects)
LOCAL_CFLAGS += -fvisibility=hidden
include $(BUILD_SHARED_LIBRARY)

View File

@@ -1,51 +0,0 @@
/*
* Copyrightm (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.
*/
#include <strings.h>
#include "AudioCodec.h"
extern AudioCodec *newAlawCodec();
extern AudioCodec *newUlawCodec();
extern AudioCodec *newGsmCodec();
extern AudioCodec *newAmrCodec();
extern AudioCodec *newGsmEfrCodec();
struct AudioCodecType {
const char *name;
AudioCodec *(*create)();
} gAudioCodecTypes[] = {
{"PCMA", newAlawCodec},
{"PCMU", newUlawCodec},
{"GSM", newGsmCodec},
{"AMR", newAmrCodec},
{"GSM-EFR", newGsmEfrCodec},
{NULL, NULL},
};
AudioCodec *newAudioCodec(const char *codecName)
{
AudioCodecType *type = gAudioCodecTypes;
while (type->name != NULL) {
if (strcasecmp(codecName, type->name) == 0) {
AudioCodec *codec = type->create();
codec->name = type->name;
return codec;
}
++type;
}
return NULL;
}

View File

@@ -1,38 +0,0 @@
/*
* Copyrightm (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.
*/
#include <stdint.h>
#ifndef __AUDIO_CODEC_H__
#define __AUDIO_CODEC_H__
class AudioCodec
{
public:
const char *name;
// Needed by destruction through base class pointers.
virtual ~AudioCodec() {}
// Returns sampleCount or non-positive value if unsupported.
virtual int set(int sampleRate, const char *fmtp) = 0;
// Returns the length of payload in bytes.
virtual int encode(void *payload, int16_t *samples) = 0;
// Returns the number of decoded samples.
virtual int decode(int16_t *samples, int count, void *payload, int length) = 0;
};
AudioCodec *newAudioCodec(const char *codecName);
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,196 +0,0 @@
/*
* Copyrightm (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.
*/
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#define LOG_TAG "Echo"
#include <utils/Log.h>
#include "EchoSuppressor.h"
// It is very difficult to do echo cancellation at this level due to the lack of
// the timing information of the samples being played and recorded. Therefore,
// for the first release only echo suppression is implemented.
// The algorithm is derived from the "previous works" summarized in
// A new class of doubletalk detectors based on cross-correlation,
// J Benesty, DR Morgan, JH Cho, IEEE Trans. on Speech and Audio Processing.
// The method proposed in that paper is not used because of its high complexity.
// It is well known that cross-correlation can be computed using convolution,
// but unfortunately not every mobile processor has a (fast enough) FPU. Thus
// we use integer arithmetic as much as possible and do lots of bookkeeping.
// Again, parameters and thresholds are chosen by experiments.
EchoSuppressor::EchoSuppressor(int sampleCount, int tailLength)
{
tailLength += sampleCount * 4;
int shift = 0;
while ((sampleCount >> shift) > 1 && (tailLength >> shift) > 256) {
++shift;
}
mShift = shift + 4;
mScale = 1 << shift;
mSampleCount = sampleCount;
mWindowSize = sampleCount >> shift;
mTailLength = tailLength >> shift;
mRecordLength = tailLength * 2 / sampleCount;
mRecordOffset = 0;
mXs = new uint16_t[mTailLength + mWindowSize];
memset(mXs, 0, sizeof(*mXs) * (mTailLength + mWindowSize));
mXSums = new uint32_t[mTailLength];
memset(mXSums, 0, sizeof(*mXSums) * mTailLength);
mX2Sums = new uint32_t[mTailLength];
memset(mX2Sums, 0, sizeof(*mX2Sums) * mTailLength);
mXRecords = new uint16_t[mRecordLength * mWindowSize];
memset(mXRecords, 0, sizeof(*mXRecords) * mRecordLength * mWindowSize);
mYSum = 0;
mY2Sum = 0;
mYRecords = new uint32_t[mRecordLength];
memset(mYRecords, 0, sizeof(*mYRecords) * mRecordLength);
mY2Records = new uint32_t[mRecordLength];
memset(mY2Records, 0, sizeof(*mY2Records) * mRecordLength);
mXYSums = new uint32_t[mTailLength];
memset(mXYSums, 0, sizeof(*mXYSums) * mTailLength);
mXYRecords = new uint32_t[mRecordLength * mTailLength];
memset(mXYRecords, 0, sizeof(*mXYRecords) * mRecordLength * mTailLength);
mLastX = 0;
mLastY = 0;
mWeight = 1.0f / (mRecordLength * mWindowSize);
}
EchoSuppressor::~EchoSuppressor()
{
delete [] mXs;
delete [] mXSums;
delete [] mX2Sums;
delete [] mXRecords;
delete [] mYRecords;
delete [] mY2Records;
delete [] mXYSums;
delete [] mXYRecords;
}
void EchoSuppressor::run(int16_t *playbacked, int16_t *recorded)
{
// Update Xs.
for (int i = mTailLength - 1; i >= 0; --i) {
mXs[i + mWindowSize] = mXs[i];
}
for (int i = mWindowSize - 1, j = 0; i >= 0; --i, j += mScale) {
uint32_t sum = 0;
for (int k = 0; k < mScale; ++k) {
int32_t x = playbacked[j + k] << 15;
mLastX += x;
sum += ((mLastX >= 0) ? mLastX : -mLastX) >> 15;
mLastX -= (mLastX >> 10) + x;
}
mXs[i] = sum >> mShift;
}
// Update XSums, X2Sums, and XRecords.
for (int i = mTailLength - mWindowSize - 1; i >= 0; --i) {
mXSums[i + mWindowSize] = mXSums[i];
mX2Sums[i + mWindowSize] = mX2Sums[i];
}
uint16_t *xRecords = &mXRecords[mRecordOffset * mWindowSize];
for (int i = mWindowSize - 1; i >= 0; --i) {
uint16_t x = mXs[i];
mXSums[i] = mXSums[i + 1] + x - xRecords[i];
mX2Sums[i] = mX2Sums[i + 1] + x * x - xRecords[i] * xRecords[i];
xRecords[i] = x;
}
// Compute Ys.
uint16_t ys[mWindowSize];
for (int i = mWindowSize - 1, j = 0; i >= 0; --i, j += mScale) {
uint32_t sum = 0;
for (int k = 0; k < mScale; ++k) {
int32_t y = recorded[j + k] << 15;
mLastY += y;
sum += ((mLastY >= 0) ? mLastY : -mLastY) >> 15;
mLastY -= (mLastY >> 10) + y;
}
ys[i] = sum >> mShift;
}
// Update YSum, Y2Sum, YRecords, and Y2Records.
uint32_t ySum = 0;
uint32_t y2Sum = 0;
for (int i = mWindowSize - 1; i >= 0; --i) {
ySum += ys[i];
y2Sum += ys[i] * ys[i];
}
mYSum += ySum - mYRecords[mRecordOffset];
mY2Sum += y2Sum - mY2Records[mRecordOffset];
mYRecords[mRecordOffset] = ySum;
mY2Records[mRecordOffset] = y2Sum;
// Update XYSums and XYRecords.
uint32_t *xyRecords = &mXYRecords[mRecordOffset * mTailLength];
for (int i = mTailLength - 1; i >= 0; --i) {
uint32_t xySum = 0;
for (int j = mWindowSize - 1; j >= 0; --j) {
xySum += mXs[i + j] * ys[j];
}
mXYSums[i] += xySum - xyRecords[i];
xyRecords[i] = xySum;
}
// Compute correlations.
int latency = 0;
float corr2 = 0.0f;
float varX = 0.0f;
float varY = mY2Sum - mWeight * mYSum * mYSum;
for (int i = mTailLength - 1; i >= 0; --i) {
float cov = mXYSums[i] - mWeight * mXSums[i] * mYSum;
if (cov > 0.0f) {
float varXi = mX2Sums[i] - mWeight * mXSums[i] * mXSums[i];
float corr2i = cov * cov / (varXi * varY + 1);
if (corr2i > corr2) {
varX = varXi;
corr2 = corr2i;
latency = i;
}
}
}
//ALOGI("corr^2 %.5f, var %8.0f %8.0f, latency %d", corr2, varX, varY,
// latency * mScale);
// Do echo suppression.
if (corr2 > 0.1f && varX > 10000.0f) {
int factor = (corr2 > 1.0f) ? 0 : (1.0f - sqrtf(corr2)) * 4096;
for (int i = 0; i < mSampleCount; ++i) {
recorded[i] = recorded[i] * factor >> 16;
}
}
// Increase RecordOffset.
++mRecordOffset;
if (mRecordOffset == mRecordLength) {
mRecordOffset = 0;
}
}

View File

@@ -1,58 +0,0 @@
/*
* Copyrightm (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.
*/
#ifndef __ECHO_SUPPRESSOR_H__
#define __ECHO_SUPPRESSOR_H__
#include <stdint.h>
class EchoSuppressor
{
public:
// The sampleCount must be power of 2.
EchoSuppressor(int sampleCount, int tailLength);
~EchoSuppressor();
void run(int16_t *playbacked, int16_t *recorded);
private:
int mShift;
int mScale;
int mSampleCount;
int mWindowSize;
int mTailLength;
int mRecordLength;
int mRecordOffset;
uint16_t *mXs;
uint32_t *mXSums;
uint32_t *mX2Sums;
uint16_t *mXRecords;
uint32_t mYSum;
uint32_t mY2Sum;
uint32_t *mYRecords;
uint32_t *mY2Records;
uint32_t *mXYSums;
uint32_t *mXYRecords;
int32_t mLastX;
int32_t mLastY;
float mWeight;
};
#endif

View File

@@ -1,144 +0,0 @@
/*
* Copyrightm (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.
*/
#include "AudioCodec.h"
namespace {
const int8_t gExponents[128] = {
0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
};
//------------------------------------------------------------------------------
class UlawCodec : public AudioCodec
{
public:
int set(int sampleRate, const char *fmtp) {
mSampleCount = sampleRate / 50;
return mSampleCount;
}
int encode(void *payload, int16_t *samples);
int decode(int16_t *samples, int count, void *payload, int length);
private:
int mSampleCount;
};
int UlawCodec::encode(void *payload, int16_t *samples)
{
int8_t *ulaws = (int8_t *)payload;
for (int i = 0; i < mSampleCount; ++i) {
int sample = samples[i];
int sign = (sample >> 8) & 0x80;
if (sample < 0) {
sample = -sample;
}
sample += 132;
if (sample > 32767) {
sample = 32767;
}
int exponent = gExponents[sample >> 8];
int mantissa = (sample >> (exponent + 3)) & 0x0F;
ulaws[i] = ~(sign | (exponent << 4) | mantissa);
}
return mSampleCount;
}
int UlawCodec::decode(int16_t *samples, int count, void *payload, int length)
{
int8_t *ulaws = (int8_t *)payload;
if (length > count) {
length = count;
}
for (int i = 0; i < length; ++i) {
int ulaw = ~ulaws[i];
int exponent = (ulaw >> 4) & 0x07;
int mantissa = ulaw & 0x0F;
int sample = (((mantissa << 3) + 132) << exponent) - 132;
samples[i] = (ulaw < 0 ? -sample : sample);
}
return length;
}
//------------------------------------------------------------------------------
class AlawCodec : public AudioCodec
{
public:
int set(int sampleRate, const char *fmtp) {
mSampleCount = sampleRate / 50;
return mSampleCount;
}
int encode(void *payload, int16_t *samples);
int decode(int16_t *samples, int count, void *payload, int length);
private:
int mSampleCount;
};
int AlawCodec::encode(void *payload, int16_t *samples)
{
int8_t *alaws = (int8_t *)payload;
for (int i = 0; i < mSampleCount; ++i) {
int sample = samples[i];
int sign = (sample >> 8) & 0x80;
if (sample < 0) {
sample = -sample;
}
if (sample > 32767) {
sample = 32767;
}
int exponent = gExponents[sample >> 8];
int mantissa = (sample >> (exponent == 0 ? 4 : exponent + 3)) & 0x0F;
alaws[i] = (sign | (exponent << 4) | mantissa) ^ 0xD5;
}
return mSampleCount;
}
int AlawCodec::decode(int16_t *samples, int count, void *payload, int length)
{
int8_t *alaws = (int8_t *)payload;
if (length > count) {
length = count;
}
for (int i = 0; i < length; ++i) {
int alaw = alaws[i] ^ 0x55;
int exponent = (alaw >> 4) & 0x07;
int mantissa = alaw & 0x0F;
int sample = (exponent == 0 ? (mantissa << 4) + 8 :
((mantissa << 3) + 132) << exponent);
samples[i] = (alaw < 0 ? sample : -sample);
}
return length;
}
} // namespace
AudioCodec *newUlawCodec()
{
return new UlawCodec;
}
AudioCodec *newAlawCodec()
{
return new AlawCodec;
}

View File

@@ -1,78 +0,0 @@
/*
* Copyrightm (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.
*/
#include "AudioCodec.h"
extern "C" {
#include "gsm.h"
}
namespace {
class GsmCodec : public AudioCodec
{
public:
GsmCodec() {
mEncode = gsm_create();
mDecode = gsm_create();
}
~GsmCodec() {
if (mEncode) {
gsm_destroy(mEncode);
}
if (mDecode) {
gsm_destroy(mDecode);
}
}
int set(int sampleRate, const char *fmtp) {
return (sampleRate == 8000 && mEncode && mDecode) ? 160 : -1;
}
int encode(void *payload, int16_t *samples);
int decode(int16_t *samples, int count, void *payload, int length);
private:
gsm mEncode;
gsm mDecode;
};
int GsmCodec::encode(void *payload, int16_t *samples)
{
gsm_encode(mEncode, samples, (unsigned char *)payload);
return 33;
}
int GsmCodec::decode(int16_t *samples, int count, void *payload, int length)
{
unsigned char *bytes = (unsigned char *)payload;
int n = 0;
while (n + 160 <= count && length >= 33 &&
gsm_decode(mDecode, bytes, &samples[n]) == 0) {
n += 160;
length -= 33;
bytes += 33;
}
return n;
}
} // namespace
AudioCodec *newGsmCodec()
{
return new GsmCodec;
}

View File

@@ -1,113 +0,0 @@
/*
* 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.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define LOG_TAG "RtpStream"
#include <utils/Log.h>
#include "jni.h"
#include "JNIHelp.h"
extern int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss);
namespace {
jfieldID gSocket;
jint create(JNIEnv *env, jobject thiz, jstring jAddress)
{
env->SetIntField(thiz, gSocket, -1);
sockaddr_storage ss;
if (parse(env, jAddress, 0, &ss) < 0) {
// Exception already thrown.
return -1;
}
int socket = ::socket(ss.ss_family, SOCK_DGRAM, 0);
socklen_t len = sizeof(ss);
if (socket == -1 || bind(socket, (sockaddr *)&ss, sizeof(ss)) != 0 ||
getsockname(socket, (sockaddr *)&ss, &len) != 0) {
jniThrowException(env, "java/net/SocketException", strerror(errno));
::close(socket);
return -1;
}
uint16_t *p = (ss.ss_family == AF_INET) ?
&((sockaddr_in *)&ss)->sin_port : &((sockaddr_in6 *)&ss)->sin6_port;
uint16_t port = ntohs(*p);
if ((port & 1) == 0) {
env->SetIntField(thiz, gSocket, socket);
return port;
}
::close(socket);
socket = ::socket(ss.ss_family, SOCK_DGRAM, 0);
if (socket != -1) {
uint16_t delta = port << 1;
++port;
for (int i = 0; i < 1000; ++i) {
do {
port += delta;
} while (port < 1024);
*p = htons(port);
if (bind(socket, (sockaddr *)&ss, sizeof(ss)) == 0) {
env->SetIntField(thiz, gSocket, socket);
return port;
}
}
}
jniThrowException(env, "java/net/SocketException", strerror(errno));
::close(socket);
return -1;
}
void close(JNIEnv *env, jobject thiz)
{
int socket = env->GetIntField(thiz, gSocket);
::close(socket);
env->SetIntField(thiz, gSocket, -1);
}
JNINativeMethod gMethods[] = {
{"create", "(Ljava/lang/String;)I", (void *)create},
{"close", "()V", (void *)close},
};
} // namespace
int registerRtpStream(JNIEnv *env)
{
jclass clazz;
if ((clazz = env->FindClass("android/net/rtp/RtpStream")) == NULL ||
(gSocket = env->GetFieldID(clazz, "mSocket", "I")) == NULL ||
env->RegisterNatives(clazz, gMethods, NELEM(gMethods)) < 0) {
ALOGE("JNI registration failed");
return -1;
}
return 0;
}

View File

@@ -1,32 +0,0 @@
/*
* 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.
*/
#include <stdio.h>
#include "jni.h"
extern int registerRtpStream(JNIEnv *env);
extern int registerAudioGroup(JNIEnv *env);
__attribute__((visibility("default"))) jint JNI_OnLoad(JavaVM *vm, void *unused)
{
JNIEnv *env = NULL;
if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK ||
registerRtpStream(env) < 0 || registerAudioGroup(env) < 0) {
return -1;
}
return JNI_VERSION_1_4;
}

View File

@@ -1,61 +0,0 @@
/*
* 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.
*/
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "jni.h"
#include "JNIHelp.h"
int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss)
{
if (!jAddress) {
jniThrowNullPointerException(env, "address");
return -1;
}
if (port < 0 || port > 65535) {
jniThrowException(env, "java/lang/IllegalArgumentException", "port");
return -1;
}
const char *address = env->GetStringUTFChars(jAddress, NULL);
if (!address) {
// Exception already thrown.
return -1;
}
memset(ss, 0, sizeof(*ss));
sockaddr_in *sin = (sockaddr_in *)ss;
if (inet_pton(AF_INET, address, &(sin->sin_addr)) > 0) {
sin->sin_family = AF_INET;
sin->sin_port = htons(port);
env->ReleaseStringUTFChars(jAddress, address);
return 0;
}
sockaddr_in6 *sin6 = (sockaddr_in6 *)ss;
if (inet_pton(AF_INET6, address, &(sin6->sin6_addr)) > 0) {
sin6->sin6_family = AF_INET6;
sin6->sin6_port = htons(port);
env->ReleaseStringUTFChars(jAddress, address);
return 0;
}
env->ReleaseStringUTFChars(jAddress, address);
jniThrowException(env, "java/lang/IllegalArgumentException", "address");
return -1;
}