Merge "Delete frameworks/base/voip use voip-common from frameworks/opt/net/voip"
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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() {
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user