summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.xml110
-rw-r--r--cmds/keystore/keystore.c2
-rw-r--r--cmds/keystore/keystore_get.h5
-rw-r--r--core/java/android/app/DownloadManager.java110
-rw-r--r--core/java/android/hardware/Camera.java54
-rw-r--r--core/java/android/provider/Downloads.java54
-rw-r--r--core/java/android/widget/PopupWindow.java30
-rw-r--r--core/java/android/widget/TextView.java60
-rw-r--r--core/res/res/drawable-hdpi/overscroll_edge.pngbin4515 -> 4861 bytes
-rw-r--r--core/res/res/drawable-hdpi/overscroll_glow.pngbin44675 -> 54467 bytes
-rw-r--r--core/tests/coretests/src/android/app/DownloadManagerIntegrationTest.java8
-rw-r--r--core/tests/coretests/src/android/app/DownloadManagerStressTest.java2
-rw-r--r--core/tests/coretests/src/android/text/TextUtilsTest.java34
-rw-r--r--native/include/android/configuration.h4
-rwxr-xr-xpackages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.pngbin4089 -> 1822 bytes
-rw-r--r--packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.pngbin1058 -> 313 bytes
-rw-r--r--packages/TtsService/Android.mk2
-rwxr-xr-xtelephony/java/com/android/internal/telephony/sip/SipPhone.java66
-rw-r--r--voip/java/android/net/rtp/AudioCodec.java3
-rw-r--r--voip/java/android/net/sip/SipManager.java24
-rw-r--r--voip/java/com/android/server/sip/SipService.java211
-rw-r--r--voip/java/com/android/server/sip/SipSessionGroup.java17
-rw-r--r--voip/jni/rtp/AmrCodec.cpp171
-rw-r--r--voip/jni/rtp/AudioCodec.cpp2
-rw-r--r--voip/jni/rtp/AudioGroup.cpp84
-rw-r--r--voip/jni/rtp/G711Codec.cpp2
-rw-r--r--voip/jni/rtp/RtpStream.cpp9
27 files changed, 750 insertions, 314 deletions
diff --git a/api/current.xml b/api/current.xml
index a37a533..eab9c7f 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -24605,55 +24605,55 @@
visibility="public"
>
</field>
-<field name="COLUMN_ERROR_CODE"
+<field name="COLUMN_ID"
type="java.lang.String"
transient="false"
volatile="false"
- value="&quot;error_code&quot;"
+ value="&quot;_id&quot;"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
-<field name="COLUMN_ID"
+<field name="COLUMN_LAST_MODIFIED_TIMESTAMP"
type="java.lang.String"
transient="false"
volatile="false"
- value="&quot;_id&quot;"
+ value="&quot;last_modified_timestamp&quot;"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
-<field name="COLUMN_LAST_MODIFIED_TIMESTAMP"
+<field name="COLUMN_LOCAL_URI"
type="java.lang.String"
transient="false"
volatile="false"
- value="&quot;last_modified_timestamp&quot;"
+ value="&quot;local_uri&quot;"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
-<field name="COLUMN_LOCAL_URI"
+<field name="COLUMN_MEDIA_TYPE"
type="java.lang.String"
transient="false"
volatile="false"
- value="&quot;local_uri&quot;"
+ value="&quot;media_type&quot;"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
-<field name="COLUMN_MEDIA_TYPE"
+<field name="COLUMN_REASON"
type="java.lang.String"
transient="false"
volatile="false"
- value="&quot;media_type&quot;"
+ value="&quot;reason&quot;"
static="true"
final="true"
deprecated="not deprecated"
@@ -24814,6 +24814,50 @@
visibility="public"
>
</field>
+<field name="PAUSED_QUEUED_FOR_WIFI"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PAUSED_UNKNOWN"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PAUSED_WAITING_FOR_NETWORK"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PAUSED_WAITING_TO_RETRY"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="STATUS_FAILED"
type="int"
transient="false"
@@ -267165,9 +267209,9 @@
deprecated="not deprecated"
visibility="protected"
>
-<parameter name="url1" type="java.net.URL">
+<parameter name="a" type="java.net.URL">
</parameter>
-<parameter name="url2" type="java.net.URL">
+<parameter name="b" type="java.net.URL">
</parameter>
</method>
<method name="openConnection"
@@ -289920,7 +289964,7 @@
>
<parameter name="parameterName" type="java.lang.String">
</parameter>
-<parameter name="x" type="java.sql.Blob">
+<parameter name="blob" type="java.sql.Blob">
</parameter>
<exception name="SQLException" type="java.sql.SQLException">
</exception>
@@ -290079,7 +290123,7 @@
>
<parameter name="parameterName" type="java.lang.String">
</parameter>
-<parameter name="x" type="java.sql.Clob">
+<parameter name="clob" type="java.sql.Clob">
</parameter>
<exception name="SQLException" type="java.sql.SQLException">
</exception>
@@ -290217,7 +290261,7 @@
>
<parameter name="parameterName" type="java.lang.String">
</parameter>
-<parameter name="value" type="java.io.Reader">
+<parameter name="reader" type="java.io.Reader">
</parameter>
<parameter name="length" type="long">
</parameter>
@@ -290253,7 +290297,7 @@
>
<parameter name="parameterName" type="java.lang.String">
</parameter>
-<parameter name="value" type="java.sql.NClob">
+<parameter name="nclob" type="java.sql.NClob">
</parameter>
<exception name="SQLException" type="java.sql.SQLException">
</exception>
@@ -290306,7 +290350,7 @@
>
<parameter name="parameterName" type="java.lang.String">
</parameter>
-<parameter name="value" type="java.lang.String">
+<parameter name="string" type="java.lang.String">
</parameter>
<exception name="SQLException" type="java.sql.SQLException">
</exception>
@@ -290416,7 +290460,7 @@
>
<parameter name="parameterName" type="java.lang.String">
</parameter>
-<parameter name="x" type="java.sql.RowId">
+<parameter name="rowId" type="java.sql.RowId">
</parameter>
<exception name="SQLException" type="java.sql.SQLException">
</exception>
@@ -290433,7 +290477,7 @@
>
<parameter name="parameterName" type="java.lang.String">
</parameter>
-<parameter name="xmlObject" type="java.sql.SQLXML">
+<parameter name="sqlXml" type="java.sql.SQLXML">
</parameter>
<exception name="SQLException" type="java.sql.SQLException">
</exception>
@@ -295569,7 +295613,7 @@
>
<parameter name="parameterIndex" type="int">
</parameter>
-<parameter name="x" type="java.io.InputStream">
+<parameter name="inputStream" type="java.io.InputStream">
</parameter>
<parameter name="length" type="long">
</parameter>
@@ -295588,7 +295632,7 @@
>
<parameter name="parameterIndex" type="int">
</parameter>
-<parameter name="x" type="java.io.InputStream">
+<parameter name="inputStream" type="java.io.InputStream">
</parameter>
<exception name="SQLException" type="java.sql.SQLException">
</exception>
@@ -295641,7 +295685,7 @@
>
<parameter name="parameterIndex" type="int">
</parameter>
-<parameter name="x" type="java.io.InputStream">
+<parameter name="inputStream" type="java.io.InputStream">
</parameter>
<parameter name="length" type="long">
</parameter>
@@ -295660,7 +295704,7 @@
>
<parameter name="parameterIndex" type="int">
</parameter>
-<parameter name="x" type="java.io.InputStream">
+<parameter name="inputStream" type="java.io.InputStream">
</parameter>
<exception name="SQLException" type="java.sql.SQLException">
</exception>
@@ -295993,7 +296037,7 @@
>
<parameter name="parameterIndex" type="int">
</parameter>
-<parameter name="value" type="java.io.Reader">
+<parameter name="reader" type="java.io.Reader">
</parameter>
<parameter name="length" type="long">
</parameter>
@@ -296012,7 +296056,7 @@
>
<parameter name="parameterIndex" type="int">
</parameter>
-<parameter name="value" type="java.io.Reader">
+<parameter name="reader" type="java.io.Reader">
</parameter>
<exception name="SQLException" type="java.sql.SQLException">
</exception>
@@ -296082,7 +296126,7 @@
>
<parameter name="parameterIndex" type="int">
</parameter>
-<parameter name="value" type="java.lang.String">
+<parameter name="theString" type="java.lang.String">
</parameter>
<exception name="SQLException" type="java.sql.SQLException">
</exception>
@@ -296209,7 +296253,7 @@
>
<parameter name="parameterIndex" type="int">
</parameter>
-<parameter name="x" type="java.sql.RowId">
+<parameter name="theRowId" type="java.sql.RowId">
</parameter>
<exception name="SQLException" type="java.sql.SQLException">
</exception>
@@ -299203,7 +299247,7 @@
>
<parameter name="columnIndex" type="int">
</parameter>
-<parameter name="x" type="java.sql.RowId">
+<parameter name="value" type="java.sql.RowId">
</parameter>
<exception name="SQLException" type="java.sql.SQLException">
</exception>
@@ -299220,7 +299264,7 @@
>
<parameter name="columnLabel" type="java.lang.String">
</parameter>
-<parameter name="x" type="java.sql.RowId">
+<parameter name="value" type="java.sql.RowId">
</parameter>
<exception name="SQLException" type="java.sql.SQLException">
</exception>
@@ -301552,7 +301596,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="x" type="java.sql.NClob">
+<parameter name="theNClob" type="java.sql.NClob">
</parameter>
<exception name="SQLException" type="java.sql.SQLException">
</exception>
@@ -301567,7 +301611,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="x" type="java.lang.String">
+<parameter name="theString" type="java.lang.String">
</parameter>
<exception name="SQLException" type="java.sql.SQLException">
</exception>
@@ -301612,7 +301656,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="x" type="java.sql.RowId">
+<parameter name="theRowId" type="java.sql.RowId">
</parameter>
<exception name="SQLException" type="java.sql.SQLException">
</exception>
@@ -301627,7 +301671,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="x" type="java.sql.SQLXML">
+<parameter name="theXml" type="java.sql.SQLXML">
</parameter>
<exception name="SQLException" type="java.sql.SQLException">
</exception>
diff --git a/cmds/keystore/keystore.c b/cmds/keystore/keystore.c
index 60cc521..971a177 100644
--- a/cmds/keystore/keystore.c
+++ b/cmds/keystore/keystore.c
@@ -166,7 +166,7 @@ static int8_t encrypt_blob(char *name, AES_KEY *aes_key)
int length;
int fd;
- if (read(the_entropy, vector, AES_BLOCK_SIZE) != AES_BLOCK_SIZE) {
+ if (read(the_entropy, blob.vector, AES_BLOCK_SIZE) != AES_BLOCK_SIZE) {
return SYSTEM_ERROR;
}
diff --git a/cmds/keystore/keystore_get.h b/cmds/keystore/keystore_get.h
index 141f69b..4b4923e 100644
--- a/cmds/keystore/keystore_get.h
+++ b/cmds/keystore/keystore_get.h
@@ -32,7 +32,7 @@ extern "C" {
#endif
/* This function is provided for native components to get values from keystore.
- * Users are required to link against libcutils. Keys are values are 8-bit safe.
+ * Users are required to link against libcutils. Keys and values are 8-bit safe.
* The first two arguments are the key and its length. The third argument
* specifies the buffer to store the retrieved value, which must be an array of
* KEYSTORE_MESSAGE_SIZE bytes. This function returns the length of the value or
@@ -65,7 +65,10 @@ static int keystore_get(const char *key, int length, char *value)
}
offset += n;
}
+ } else {
+ length = -1;
}
+
close(sock);
return length;
}
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index c6fef4f..3ec21ff 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -100,16 +100,23 @@ public class DownloadManager {
public final static String COLUMN_STATUS = "status";
/**
- * Indicates the type of error that occurred, when {@link #COLUMN_STATUS} is
- * {@link #STATUS_FAILED}. If an HTTP error occurred, this will hold the HTTP status code as
- * defined in RFC 2616. Otherwise, it will hold one of the ERROR_* constants.
+ * Provides more detail on the status of the download. Its meaning depends on the value of
+ * {@link #COLUMN_STATUS}.
*
- * If {@link #COLUMN_STATUS} is not {@link #STATUS_FAILED}, this column's value is undefined.
+ * When {@link #COLUMN_STATUS} is {@link #STATUS_FAILED}, this indicates the type of error that
+ * occurred. If an HTTP error occurred, this will hold the HTTP status code as defined in RFC
+ * 2616. Otherwise, it will hold one of the ERROR_* constants.
+ *
+ * When {@link #COLUMN_STATUS} is {@link #STATUS_PAUSED}, this indicates why the download is
+ * paused. It will hold one of the PAUSED_* constants.
+ *
+ * If {@link #COLUMN_STATUS} is neither {@link #STATUS_FAILED} nor {@link #STATUS_PAUSED}, this
+ * column's value is undefined.
*
* @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1">RFC 2616
* status codes</a>
*/
- public final static String COLUMN_ERROR_CODE = "error_code";
+ public final static String COLUMN_REASON = "reason";
/**
* Number of bytes download so far.
@@ -156,54 +163,77 @@ public class DownloadManager {
public final static int ERROR_UNKNOWN = 1000;
/**
- * Value of {@link #COLUMN_ERROR_CODE} when a storage issue arises which doesn't fit under any
+ * Value of {@link #COLUMN_REASON} when a storage issue arises which doesn't fit under any
* other error code. Use the more specific {@link #ERROR_INSUFFICIENT_SPACE} and
* {@link #ERROR_DEVICE_NOT_FOUND} when appropriate.
*/
public final static int ERROR_FILE_ERROR = 1001;
/**
- * Value of {@link #COLUMN_ERROR_CODE} when an HTTP code was received that download manager
+ * Value of {@link #COLUMN_REASON} when an HTTP code was received that download manager
* can't handle.
*/
public final static int ERROR_UNHANDLED_HTTP_CODE = 1002;
/**
- * Value of {@link #COLUMN_ERROR_CODE} when an error receiving or processing data occurred at
+ * Value of {@link #COLUMN_REASON} when an error receiving or processing data occurred at
* the HTTP level.
*/
public final static int ERROR_HTTP_DATA_ERROR = 1004;
/**
- * Value of {@link #COLUMN_ERROR_CODE} when there were too many redirects.
+ * Value of {@link #COLUMN_REASON} when there were too many redirects.
*/
public final static int ERROR_TOO_MANY_REDIRECTS = 1005;
/**
- * Value of {@link #COLUMN_ERROR_CODE} when there was insufficient storage space. Typically,
+ * Value of {@link #COLUMN_REASON} when there was insufficient storage space. Typically,
* this is because the SD card is full.
*/
public final static int ERROR_INSUFFICIENT_SPACE = 1006;
/**
- * Value of {@link #COLUMN_ERROR_CODE} when no external storage device was found. Typically,
+ * Value of {@link #COLUMN_REASON} when no external storage device was found. Typically,
* this is because the SD card is not mounted.
*/
public final static int ERROR_DEVICE_NOT_FOUND = 1007;
/**
- * Value of {@link #COLUMN_ERROR_CODE} when some possibly transient error occurred but we can't
+ * Value of {@link #COLUMN_REASON} when some possibly transient error occurred but we can't
* resume the download.
*/
public final static int ERROR_CANNOT_RESUME = 1008;
/**
- * Value of {@link #COLUMN_ERROR_CODE} when the requested destination file already exists (the
+ * Value of {@link #COLUMN_REASON} when the requested destination file already exists (the
* download manager will not overwrite an existing file).
*/
public final static int ERROR_FILE_ALREADY_EXISTS = 1009;
/**
+ * Value of {@link #COLUMN_REASON} when the download is paused because some network error
+ * occurred and the download manager is waiting before retrying the request.
+ */
+ public final static int PAUSED_WAITING_TO_RETRY = 1;
+
+ /**
+ * Value of {@link #COLUMN_REASON} when the download is waiting for network connectivity to
+ * proceed.
+ */
+ public final static int PAUSED_WAITING_FOR_NETWORK = 2;
+
+ /**
+ * Value of {@link #COLUMN_REASON} when the download exceeds a size limit for downloads over
+ * the mobile network and the download manager is waiting for a Wi-Fi connection to proceed.
+ */
+ public final static int PAUSED_QUEUED_FOR_WIFI = 3;
+
+ /**
+ * Value of {@link #COLUMN_REASON} when the download is paused for some other reason.
+ */
+ public final static int PAUSED_UNKNOWN = 4;
+
+ /**
* Broadcast intent action sent by the download manager when a download completes.
*/
public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE";
@@ -236,7 +266,7 @@ public class DownloadManager {
COLUMN_TOTAL_SIZE_BYTES,
COLUMN_LOCAL_URI,
COLUMN_STATUS,
- COLUMN_ERROR_CODE,
+ COLUMN_REASON,
COLUMN_BYTES_DOWNLOADED_SO_FAR,
COLUMN_LAST_MODIFIED_TIMESTAMP
};
@@ -258,7 +288,7 @@ public class DownloadManager {
};
private static final Set<String> LONG_COLUMNS = new HashSet<String>(
- Arrays.asList(COLUMN_ID, COLUMN_TOTAL_SIZE_BYTES, COLUMN_STATUS, COLUMN_ERROR_CODE,
+ Arrays.asList(COLUMN_ID, COLUMN_TOTAL_SIZE_BYTES, COLUMN_STATUS, COLUMN_REASON,
COLUMN_BYTES_DOWNLOADED_SO_FAR, COLUMN_LAST_MODIFIED_TIMESTAMP));
/**
@@ -617,8 +647,10 @@ public class DownloadManager {
parts.add(statusClause("=", Downloads.STATUS_RUNNING));
}
if ((mStatusFlags & STATUS_PAUSED) != 0) {
- parts.add(statusClause("=", Downloads.STATUS_PENDING_PAUSED));
- parts.add(statusClause("=", Downloads.STATUS_RUNNING_PAUSED));
+ parts.add(statusClause("=", Downloads.Impl.STATUS_PAUSED_BY_APP));
+ parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_TO_RETRY));
+ parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_FOR_NETWORK));
+ parts.add(statusClause("=", Downloads.Impl.STATUS_QUEUED_FOR_WIFI));
}
if ((mStatusFlags & STATUS_SUCCESSFUL) != 0) {
parts.add(statusClause("=", Downloads.STATUS_SUCCESS));
@@ -914,8 +946,8 @@ public class DownloadManager {
if (column.equals(COLUMN_STATUS)) {
return translateStatus((int) getUnderlyingLong(Downloads.COLUMN_STATUS));
}
- if (column.equals(COLUMN_ERROR_CODE)) {
- return translateErrorCode((int) getUnderlyingLong(Downloads.COLUMN_STATUS));
+ if (column.equals(COLUMN_REASON)) {
+ return getReason((int) getUnderlyingLong(Downloads.COLUMN_STATUS));
}
if (column.equals(COLUMN_BYTES_DOWNLOADED_SO_FAR)) {
return getUnderlyingLong(Downloads.COLUMN_CURRENT_BYTES);
@@ -924,10 +956,36 @@ public class DownloadManager {
return getUnderlyingLong(Downloads.COLUMN_LAST_MODIFICATION);
}
- private long translateErrorCode(int status) {
- if (translateStatus(status) != STATUS_FAILED) {
- return 0; // arbitrary value when status is not an error
+ private long getReason(int status) {
+ switch (translateStatus(status)) {
+ case STATUS_FAILED:
+ return getErrorCode(status);
+
+ case STATUS_PAUSED:
+ return getPausedReason(status);
+
+ default:
+ return 0; // arbitrary value when status is not an error
}
+ }
+
+ private long getPausedReason(int status) {
+ switch (status) {
+ case Downloads.Impl.STATUS_WAITING_TO_RETRY:
+ return PAUSED_WAITING_TO_RETRY;
+
+ case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
+ return PAUSED_WAITING_FOR_NETWORK;
+
+ case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
+ return PAUSED_QUEUED_FOR_WIFI;
+
+ default:
+ return PAUSED_UNKNOWN;
+ }
+ }
+
+ private long getErrorCode(int status) {
if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS)
|| (500 <= status && status < 600)) {
// HTTP status code
@@ -973,7 +1031,7 @@ public class DownloadManager {
return super.getString(super.getColumnIndex(column));
}
- private long translateStatus(int status) {
+ private int translateStatus(int status) {
switch (status) {
case Downloads.STATUS_PENDING:
return STATUS_PENDING;
@@ -981,8 +1039,10 @@ public class DownloadManager {
case Downloads.STATUS_RUNNING:
return STATUS_RUNNING;
- case Downloads.STATUS_PENDING_PAUSED:
- case Downloads.STATUS_RUNNING_PAUSED:
+ case Downloads.Impl.STATUS_PAUSED_BY_APP:
+ case Downloads.Impl.STATUS_WAITING_TO_RETRY:
+ case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
+ case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
return STATUS_PAUSED;
case Downloads.STATUS_SUCCESS:
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 0d8228c..7b930d5 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -181,6 +181,7 @@ public class Camera {
* value should be 90.
*
* @see #setDisplayOrientation(int)
+ * @see #setRotation(int)
*/
public int orientation;
};
@@ -1716,23 +1717,46 @@ public class Camera {
}
/**
- * Sets the orientation of the device in degrees. For example, suppose
- * the natural position of the device is landscape. If the user takes a
- * picture in landscape mode in 2048x1536 resolution, the rotation
- * should be set to 0. If the user rotates the phone 90 degrees
- * clockwise, the rotation should be set to 90. Applications can use
- * {@link android.view.OrientationEventListener} to set this parameter.
- *
- * The camera driver may set orientation in the EXIF header without
- * rotating the picture. Or the driver may rotate the picture and
- * the EXIF thumbnail. If the Jpeg picture is rotated, the orientation
- * in the EXIF header will be missing or 1 (row #0 is top and column #0
- * is left side).
- *
- * @param rotation The orientation of the device in degrees. Rotation
- * can only be 0, 90, 180 or 270.
+ * Sets the rotation angle in degrees relative to the orientation of
+ * the camera. This affects the pictures returned from JPEG {@link
+ * PictureCallback}. The camera driver may set orientation in the
+ * EXIF header without rotating the picture. Or the driver may rotate
+ * the picture and the EXIF thumbnail. If the Jpeg picture is rotated,
+ * the orientation in the EXIF header will be missing or 1 (row #0 is
+ * top and column #0 is left side).
+ *
+ * If appplications want to rotate the picture to match the
+ * orientation of what users see, apps should use {@link
+ * android.view.OrientationEventListener} and {@link CameraInfo}.
+ * The value from OrientationEventListener is relative to the natural
+ * orientation of the device. CameraInfo.mOrientation is the angle
+ * between camera orientation and natural device orientation. The sum
+ * of the two is the angle for rotation.
+ *
+ * For example, suppose the natural orientation of the device is
+ * portrait. The device is rotated 270 degrees clockwise, so the device
+ * orientation is 270. Suppose the camera sensor is mounted in landscape
+ * and the top side of the camera sensor is aligned with the right edge
+ * of the display in natural orientation. So the camera orientation is
+ * 90. The rotation should be set to 0 (270 + 90).
+ *
+ * The reference code is as follows.
+ *
+ * public void public void onOrientationChanged(int orientation) {
+ * if (orientation == ORIENTATION_UNKNOWN) return;
+ * android.hardware.Camera.CameraInfo info =
+ * new android.hardware.Camera.CameraInfo();
+ * android.hardware.Camera.getCameraInfo(cameraId, info);
+ * orientation = (orientation + 45) / 90 * 90;
+ * mParameters.setRotation((orientation + info.mOrientation) % 360);
+ * }
+ *
+ * @param rotation The rotation angle in degrees relative to the
+ * orientation of the camera. Rotation can only be 0,
+ * 90, 180 or 270.
* @throws IllegalArgumentException if rotation value is invalid.
* @see android.view.OrientationEventListener
+ * @see #getCameraInfo(int, CameraInfo)
*/
public void setRotation(int rotation) {
if (rotation == 0 || rotation == 90 || rotation == 180
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index 1e358c9..8fd0e0a 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -378,16 +378,6 @@ public final class Downloads {
}
/**
- * Returns whether the download is suspended. (i.e. whether the download
- * won't complete without some action from outside the download
- * manager).
- * @hide
- */
- public static boolean isStatusSuspended(int status) {
- return (status == STATUS_PENDING_PAUSED || status == STATUS_RUNNING_PAUSED);
- }
-
- /**
* Returns whether the status is a success (i.e. 2xx).
* @hide
*/
@@ -435,24 +425,12 @@ public final class Downloads {
public static final int STATUS_PENDING = 190;
/**
- * This download hasn't stated yet and is paused
- * @hide
- */
- public static final int STATUS_PENDING_PAUSED = 191;
-
- /**
* This download has started
* @hide
*/
public static final int STATUS_RUNNING = 192;
/**
- * This download has started and is paused
- * @hide
- */
- public static final int STATUS_RUNNING_PAUSED = 193;
-
- /**
* This download has successfully completed.
* Warning: there might be other status values that indicate success
* in the future.
@@ -980,15 +958,6 @@ public final class Downloads {
}
/**
- * Returns whether the download is suspended. (i.e. whether the download
- * won't complete without some action from outside the download
- * manager).
- */
- public static boolean isStatusSuspended(int status) {
- return (status == STATUS_PENDING_PAUSED || status == STATUS_RUNNING_PAUSED);
- }
-
- /**
* Returns whether the status is a success (i.e. 2xx).
*/
public static boolean isStatusSuccess(int status) {
@@ -1030,19 +999,30 @@ public final class Downloads {
public static final int STATUS_PENDING = 190;
/**
- * This download hasn't stated yet and is paused
+ * This download has started
*/
- public static final int STATUS_PENDING_PAUSED = 191;
+ public static final int STATUS_RUNNING = 192;
/**
- * This download has started
+ * This download has been paused by the owning app.
*/
- public static final int STATUS_RUNNING = 192;
+ public static final int STATUS_PAUSED_BY_APP = 193;
+
+ /**
+ * This download encountered some network error and is waiting before retrying the request.
+ */
+ public static final int STATUS_WAITING_TO_RETRY = 194;
+
+ /**
+ * This download is waiting for network connectivity to proceed.
+ */
+ public static final int STATUS_WAITING_FOR_NETWORK = 195;
/**
- * This download has started and is paused
+ * This download exceeded a size limit for mobile networks and is waiting for a Wi-Fi
+ * connection to proceed.
*/
- public static final int STATUS_RUNNING_PAUSED = 193;
+ public static final int STATUS_QUEUED_FOR_WIFI = 196;
/**
* This download has successfully completed.
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 7fe6190..ed15e25 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -86,6 +86,7 @@ public class PopupWindow {
private boolean mOutsideTouchable = false;
private boolean mClippingEnabled = true;
private boolean mSplitTouchEnabled;
+ private boolean mLayoutInScreen;
private OnTouchListener mTouchInterceptor;
@@ -596,6 +597,29 @@ public class PopupWindow {
}
/**
+ * <p>Indicates whether the popup window will be forced into using absolute screen coordinates
+ * for positioning.</p>
+ *
+ * @return true if the window will always be positioned in screen coordinates.
+ * @hide
+ */
+ public boolean isLayoutInScreenEnabled() {
+ return mLayoutInScreen;
+ }
+
+ /**
+ * <p>Allows the popup window to force the flag
+ * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}, overriding default behavior.
+ * This will cause the popup to be positioned in absolute screen coordinates.</p>
+ *
+ * @param enabled true if the popup should always be positioned in screen coordinates
+ * @hide
+ */
+ public void setLayoutInScreenEnabled(boolean enabled) {
+ mLayoutInScreen = enabled;
+ }
+
+ /**
* <p>Change the width and height measure specs that are given to the
* window manager by the popup. By default these are 0, meaning that
* the current width or height is requested as an explicit size from
@@ -899,7 +923,8 @@ public class PopupWindow {
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
- WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
+ WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);
if(mIgnoreCheekPress) {
curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
}
@@ -923,6 +948,9 @@ public class PopupWindow {
if (mSplitTouchEnabled) {
curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
}
+ if (mLayoutInScreen) {
+ curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+ }
return curFlags;
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 6278192..8291d57 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3768,6 +3768,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mError != null) {
hideError();
}
+
+ hideControllers();
}
@Override
@@ -4118,6 +4120,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
canvas.restore();
+
+ if (mInsertionPointCursorController != null &&
+ mInsertionPointCursorController.isShowing()) {
+ mInsertionPointCursorController.updatePosition();
+ }
+ if (mSelectionModifierCursorController != null &&
+ mSelectionModifierCursorController.isShowing()) {
+ mSelectionModifierCursorController.updatePosition();
+ }
}
@Override
@@ -4736,6 +4747,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mInputMethodState != null) {
mInputMethodState.mExtracting = req;
}
+ hideControllers();
}
/**
@@ -6266,7 +6278,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
sendOnTextChanged(buffer, start, before, after);
onTextChanged(buffer, start, before, after);
- hideControllers();
+
+ // Hide the controller if the amount of content changed
+ if (before != after) {
+ hideControllers();
+ }
}
/**
@@ -6605,11 +6621,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mInputContentType != null) {
mInputContentType.enterDown = false;
}
+ hideControllers();
}
startStopMarquee(hasWindowFocus);
}
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ if (visibility != VISIBLE) {
+ hideControllers();
+ }
+ }
+
/**
* Use {@link BaseInputConnection#removeComposingSpans
* BaseInputConnection.removeComposingSpans()} to remove any IME composing
@@ -6679,8 +6704,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (hasSelection()) {
startTextSelectionMode();
- } else if (mInsertionPointCursorController != null) {
- mInsertionPointCursorController.show();
}
}
}
@@ -7645,6 +7668,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private int mPositionY;
private CursorController mController;
private boolean mIsDragging;
+ private int mOffsetX;
+ private int mOffsetY;
public HandleView(CursorController controller, Drawable handle) {
super(TextView.this.mContext);
@@ -7653,6 +7678,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mContainer = new PopupWindow(TextView.this.mContext, null,
com.android.internal.R.attr.textSelectHandleWindowStyle);
mContainer.setSplitTouchEnabled(true);
+ mContainer.setClippingEnabled(false);
+ mContainer.setLayoutInScreenEnabled(true);
}
@Override
@@ -7690,19 +7717,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final int compoundPaddingRight = getCompoundPaddingRight();
final TextView hostView = TextView.this;
- final int right = hostView.mRight;
- final int left = hostView.mLeft;
- final int bottom = hostView.mBottom;
- final int top = hostView.mTop;
+ final int handleWidth = mDrawable.getIntrinsicWidth();
+ final int left = 0;
+ final int right = hostView.getWidth();
+ final int top = 0;
+ final int bottom = hostView.getHeight();
- final int clipLeft = left + compoundPaddingLeft;
+ final int clipLeft = left + compoundPaddingLeft - (int) (handleWidth * 0.75f);
final int clipTop = top + extendedPaddingTop;
- final int clipRight = right - compoundPaddingRight;
+ final int clipRight = right - compoundPaddingRight + (int) (handleWidth * 0.25f);
final int clipBottom = bottom - extendedPaddingBottom;
- final int handleWidth = mDrawable.getIntrinsicWidth();
- return mPositionX >= clipLeft - handleWidth * 0.75f &&
- mPositionX <= clipRight + handleWidth * 0.25f &&
+ return mPositionX >= clipLeft && mPositionX <= clipRight &&
mPositionY >= clipTop && mPositionY <= clipBottom;
}
@@ -7741,6 +7767,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
+ mOffsetX = (int) (ev.getX() - mDrawable.getIntrinsicWidth() / 2.f + 0.5f);
+ mOffsetY = (int) (ev.getY() - mDrawable.getIntrinsicHeight() / 2.f + 0.5f);
mIsDragging = true;
break;
@@ -7749,8 +7777,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final float rawY = ev.getRawY();
final int[] coords = mTempCoords;
TextView.this.getLocationOnScreen(coords);
- final int x = (int) (rawX - coords[0] + 0.5f);
- final int y = (int) (rawY - coords[1] + 0.5f);
+ final int x = (int) (rawX - coords[0] + 0.5f) - mOffsetX;
+ final int y = (int) (rawY - coords[1] + 0.5f) -
+ (int) (mDrawable.getIntrinsicHeight() * 0.8f) - mOffsetY;
+
mController.updatePosition(this, x, y);
break;
@@ -8059,7 +8089,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final int previousLine = layout.getLineForOffset(previousOffset);
final int previousLineTop = layout.getLineTop(previousLine);
final int previousLineBottom = layout.getLineBottom(previousLine);
- final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 2;
+ final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 6;
// If new line is just before or after previous line and y position is less than
// hysteresisThreshold away from previous line, keep cursor on previous line.
diff --git a/core/res/res/drawable-hdpi/overscroll_edge.png b/core/res/res/drawable-hdpi/overscroll_edge.png
index f8e40ec..e8c1aa3 100644
--- a/core/res/res/drawable-hdpi/overscroll_edge.png
+++ b/core/res/res/drawable-hdpi/overscroll_edge.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/overscroll_glow.png b/core/res/res/drawable-hdpi/overscroll_glow.png
index a8a62c4..3418384 100644
--- a/core/res/res/drawable-hdpi/overscroll_glow.png
+++ b/core/res/res/drawable-hdpi/overscroll_glow.png
Binary files differ
diff --git a/core/tests/coretests/src/android/app/DownloadManagerIntegrationTest.java b/core/tests/coretests/src/android/app/DownloadManagerIntegrationTest.java
index 38f336e..27eea4d 100644
--- a/core/tests/coretests/src/android/app/DownloadManagerIntegrationTest.java
+++ b/core/tests/coretests/src/android/app/DownloadManagerIntegrationTest.java
@@ -120,7 +120,7 @@ public class DownloadManagerIntegrationTest extends DownloadManagerBaseTest {
Cursor cursor = getCursor(dlRequest);
try {
- verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE, error);
+ verifyInt(cursor, DownloadManager.COLUMN_REASON, error);
} finally {
cursor.close();
}
@@ -183,7 +183,7 @@ public class DownloadManagerIntegrationTest extends DownloadManagerBaseTest {
Cursor cursor = getCursor(dlRequest);
try {
verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED);
- verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+ verifyInt(cursor, DownloadManager.COLUMN_REASON,
DownloadManager.ERROR_CANNOT_RESUME);
} finally {
cursor.close();
@@ -269,7 +269,7 @@ public class DownloadManagerIntegrationTest extends DownloadManagerBaseTest {
try {
verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED);
- verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+ verifyInt(cursor, DownloadManager.COLUMN_REASON,
DownloadManager.ERROR_FILE_ERROR);
} finally {
cursor.close();
@@ -476,7 +476,7 @@ public class DownloadManagerIntegrationTest extends DownloadManagerBaseTest {
// For the last download we should have failed b/c there is not enough space left in cache
Cursor cursor = getCursor(dlRequest);
try {
- verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+ verifyInt(cursor, DownloadManager.COLUMN_REASON,
DownloadManager.ERROR_INSUFFICIENT_SPACE);
} finally {
cursor.close();
diff --git a/core/tests/coretests/src/android/app/DownloadManagerStressTest.java b/core/tests/coretests/src/android/app/DownloadManagerStressTest.java
index 4ff0295..ddf138f 100644
--- a/core/tests/coretests/src/android/app/DownloadManagerStressTest.java
+++ b/core/tests/coretests/src/android/app/DownloadManagerStressTest.java
@@ -145,7 +145,7 @@ public class DownloadManagerStressTest extends DownloadManagerBaseTest {
cursor = getCursor(dlRequest);
verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED);
- verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+ verifyInt(cursor, DownloadManager.COLUMN_REASON,
DownloadManager.ERROR_INSUFFICIENT_SPACE);
} finally {
if (cursor != null) {
diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java
index 1beba53..e111662 100644
--- a/core/tests/coretests/src/android/text/TextUtilsTest.java
+++ b/core/tests/coretests/src/android/text/TextUtilsTest.java
@@ -30,7 +30,6 @@ import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;
import android.test.MoreAsserts;
-import com.android.common.Rfc822Validator;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
@@ -238,39 +237,6 @@ public class TextUtilsTest extends TestCase {
}
}
- //==============================================================================================
- // Email validator
- //==============================================================================================
-
- @SmallTest
- public void testEmailValidator() {
- Rfc822Validator validator = new Rfc822Validator("gmail.com");
- String[] validEmails = new String[] {
- "a@b.com", "a@b.fr", "a+b@c.com", "a@b.info",
- };
-
- for (String email : validEmails) {
- assertTrue(email + " should be a valid email address", validator.isValid(email));
- }
-
- String[] invalidEmails = new String[] {
- "a", "a@b", "a b", "a@b.12"
- };
-
- for (String email : invalidEmails) {
- assertFalse(email + " should not be a valid email address", validator.isValid(email));
- }
-
- Map<String, String> fixes = Maps.newHashMap();
- fixes.put("a", "<a@gmail.com>");
- fixes.put("a b", "<ab@gmail.com>");
- fixes.put("a@b", "<a@b>");
-
- for (Map.Entry<String, String> e : fixes.entrySet()) {
- assertEquals(e.getValue(), validator.fixText(e.getKey()).toString());
- }
- }
-
@SmallTest
public void testRfc822TokenizerFullAddress() {
Rfc822Token[] tokens = Rfc822Tokenizer.tokenize("Foo Bar (something) <foo@google.com>");
diff --git a/native/include/android/configuration.h b/native/include/android/configuration.h
index 79b9b1e..99e8f97 100644
--- a/native/include/android/configuration.h
+++ b/native/include/android/configuration.h
@@ -79,8 +79,8 @@ enum {
ACONFIGURATION_UI_MODE_TYPE_CAR = 0x03,
ACONFIGURATION_UI_MODE_NIGHT_ANY = 0x00,
- ACONFIGURATION_UI_MODE_NIGHT_NO = 0x10,
- ACONFIGURATION_UI_MODE_NIGHT_YES = 0x20,
+ ACONFIGURATION_UI_MODE_NIGHT_NO = 0x1,
+ ACONFIGURATION_UI_MODE_NIGHT_YES = 0x2,
ACONFIGURATION_MCC = 0x0001,
ACONFIGURATION_MNC = 0x0002,
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.png
index c299e12..ae90cc8 100755
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png
index 3e317dd..2f66b1d 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png
Binary files differ
diff --git a/packages/TtsService/Android.mk b/packages/TtsService/Android.mk
index 75b26a2..a1a3b9f 100644
--- a/packages/TtsService/Android.mk
+++ b/packages/TtsService/Android.mk
@@ -8,7 +8,7 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files) \
LOCAL_PACKAGE_NAME := TtsService
LOCAL_CERTIFICATE := platform
-LOCAL_PROGUARD_FLAGS := -include $(LOCAL_PATH)/proguard.flags
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
include $(BUILD_PACKAGE)
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
index bceceda..af3e0886 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
@@ -126,14 +126,27 @@ public class SipPhone extends SipPhoneBase {
return false;
}
- SipAudioCall sipAudioCall = (SipAudioCall) incomingCall;
- Log.v(LOG_TAG, " ++++++ taking call from: "
- + sipAudioCall.getPeerProfile().getUriString());
- String localUri = sipAudioCall.getLocalProfile().getUriString();
- if (localUri.equals(mProfile.getUriString())) {
- boolean makeCallWait = foregroundCall.getState().isAlive();
- ringingCall.initIncomingCall(sipAudioCall, makeCallWait);
- return true;
+ try {
+ SipAudioCall sipAudioCall = (SipAudioCall) incomingCall;
+ Log.d(LOG_TAG, "+++ taking call from: "
+ + sipAudioCall.getPeerProfile().getUriString());
+ String localUri = sipAudioCall.getLocalProfile().getUriString();
+ if (localUri.equals(mProfile.getUriString())) {
+ boolean makeCallWait = foregroundCall.getState().isAlive();
+ ringingCall.initIncomingCall(sipAudioCall, makeCallWait);
+ if (sipAudioCall.getState()
+ != SipSession.State.INCOMING_CALL) {
+ // Peer cancelled the call!
+ Log.d(LOG_TAG, " call cancelled !!");
+ ringingCall.reset();
+ }
+ return true;
+ }
+ } catch (Exception e) {
+ // Peer may cancel the call at any time during the time we hook
+ // up ringingCall with sipAudioCall. Clean up ringingCall when
+ // that happens.
+ ringingCall.reset();
}
return false;
}
@@ -358,6 +371,11 @@ public class SipPhone extends SipPhoneBase {
}
private class SipCall extends SipCallBase {
+ void reset() {
+ connections.clear();
+ setState(Call.State.IDLE);
+ }
+
void switchWith(SipCall that) {
synchronized (SipPhone.class) {
SipCall tmp = new SipCall();
@@ -444,6 +462,7 @@ public class SipPhone extends SipPhoneBase {
if (state.isAlive()) {
Log.d(LOG_TAG, "hang up call: " + getState() + ": " + this
+ " on phone " + getPhone());
+ setState(State.DISCONNECTING);
CallStateException excp = null;
for (Connection c : connections) {
try {
@@ -453,7 +472,6 @@ public class SipPhone extends SipPhoneBase {
}
}
if (excp != null) throw excp;
- setState(State.DISCONNECTING);
} else {
Log.d(LOG_TAG, "hang up dead call: " + getState() + ": "
+ this + " on phone " + getPhone());
@@ -630,13 +648,20 @@ public class SipPhone extends SipPhoneBase {
}
synchronized (SipPhone.class) {
setState(Call.State.DISCONNECTED);
- mSipAudioCall.close();
- mOwner.onConnectionEnded(SipConnection.this);
- Log.v(LOG_TAG, "-------- connection ended: "
- + mPeer.getUriString() + ": "
- + mSipAudioCall.getState() + ", cause: "
- + getDisconnectCause() + ", on phone "
+ SipAudioCall sipAudioCall = mSipAudioCall;
+ mSipAudioCall = null;
+ String sessionState = (sipAudioCall == null)
+ ? ""
+ : (sipAudioCall.getState() + ", ");
+ Log.v(LOG_TAG, "--- connection ended: "
+ + mPeer.getUriString() + ": " + sessionState
+ + "cause: " + getDisconnectCause() + ", on phone "
+ getPhone());
+ if (sipAudioCall != null) {
+ sipAudioCall.setListener(null);
+ sipAudioCall.close();
+ }
+ mOwner.onConnectionEnded(SipConnection.this);
}
}
@@ -790,14 +815,17 @@ public class SipPhone extends SipPhoneBase {
synchronized (SipPhone.class) {
Log.v(LOG_TAG, "hangup conn: " + mPeer.getUriString() + ": "
+ mState + ": on phone " + getPhone().getPhoneName());
+ if (!mState.isAlive()) return;
try {
- if (mState.isAlive()) {
- if (mSipAudioCall != null) mSipAudioCall.endCall();
- setState(Call.State.DISCONNECTING);
- setDisconnectCause(DisconnectCause.LOCAL);
+ SipAudioCall sipAudioCall = mSipAudioCall;
+ if (sipAudioCall != null) {
+ sipAudioCall.setListener(null);
+ sipAudioCall.endCall();
}
} catch (SipException e) {
throw new CallStateException("hangup(): " + e);
+ } finally {
+ mAdapter.onCallEnded(DisconnectCause.LOCAL);
}
}
}
diff --git a/voip/java/android/net/rtp/AudioCodec.java b/voip/java/android/net/rtp/AudioCodec.java
index f171806..3877aeb 100644
--- a/voip/java/android/net/rtp/AudioCodec.java
+++ b/voip/java/android/net/rtp/AudioCodec.java
@@ -80,8 +80,7 @@ public class AudioCodec {
*/
public static final AudioCodec AMR = new AudioCodec(97, "AMR/8000", null);
- // TODO: add rest of the codecs when the native part is done.
- private static final AudioCodec[] sCodecs = {GSM_EFR, GSM, PCMU, PCMA};
+ private static final AudioCodec[] sCodecs = {GSM_EFR, AMR, GSM, PCMU, PCMA};
private AudioCodec(int type, String rtpmap, String fmtp) {
this.type = type;
diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java
index 59631c1..a589fe9 100644
--- a/voip/java/android/net/sip/SipManager.java
+++ b/voip/java/android/net/sip/SipManager.java
@@ -173,7 +173,7 @@ public class SipManager {
SipRegistrationListener listener) throws SipException {
try {
mSipService.open3(localProfile, incomingCallBroadcastAction,
- createRelay(listener));
+ createRelay(listener, localProfile.getUriString()));
} catch (RemoteException e) {
throw new SipException("open()", e);
}
@@ -191,7 +191,7 @@ public class SipManager {
SipRegistrationListener listener) throws SipException {
try {
mSipService.setRegistrationListener(
- localProfileUri, createRelay(listener));
+ localProfileUri, createRelay(listener, localProfileUri));
} catch (RemoteException e) {
throw new SipException("setRegistrationListener()", e);
}
@@ -425,8 +425,8 @@ public class SipManager {
public void register(SipProfile localProfile, int expiryTime,
SipRegistrationListener listener) throws SipException {
try {
- ISipSession session = mSipService.createSession(
- localProfile, createRelay(listener));
+ ISipSession session = mSipService.createSession(localProfile,
+ createRelay(listener, localProfile.getUriString()));
session.register(expiryTime);
} catch (RemoteException e) {
throw new SipException("register()", e);
@@ -446,8 +446,8 @@ public class SipManager {
public void unregister(SipProfile localProfile,
SipRegistrationListener listener) throws SipException {
try {
- ISipSession session = mSipService.createSession(
- localProfile, createRelay(listener));
+ ISipSession session = mSipService.createSession(localProfile,
+ createRelay(listener, localProfile.getUriString()));
session.unregister();
} catch (RemoteException e) {
throw new SipException("unregister()", e);
@@ -475,8 +475,8 @@ public class SipManager {
}
private static ISipSessionListener createRelay(
- SipRegistrationListener listener) {
- return ((listener == null) ? null : new ListenerRelay(listener));
+ SipRegistrationListener listener, String uri) {
+ return ((listener == null) ? null : new ListenerRelay(listener, uri));
}
/**
@@ -512,15 +512,19 @@ public class SipManager {
private static class ListenerRelay extends SipSessionAdapter {
private SipRegistrationListener mListener;
+ private String mUri;
// listener must not be null
- public ListenerRelay(SipRegistrationListener listener) {
+ public ListenerRelay(SipRegistrationListener listener, String uri) {
mListener = listener;
+ mUri = uri;
}
private String getUri(ISipSession session) {
try {
- return session.getLocalProfile().getUriString();
+ return ((session == null)
+ ? mUri
+ : session.getLocalProfile().getUriString());
} catch (RemoteException e) {
throw new RuntimeException(e);
}
diff --git a/voip/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java
index 0ff5586..aff1439 100644
--- a/voip/java/com/android/server/sip/SipService.java
+++ b/voip/java/com/android/server/sip/SipService.java
@@ -39,6 +39,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -49,6 +50,7 @@ import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
@@ -119,17 +121,19 @@ public final class SipService extends ISipService.Stub {
}
public synchronized SipProfile[] getListOfProfiles() {
- SipProfile[] profiles = new SipProfile[mSipGroups.size()];
- int i = 0;
+ boolean isCallerRadio = isCallerRadio();
+ ArrayList<SipProfile> profiles = new ArrayList<SipProfile>();
for (SipSessionGroupExt group : mSipGroups.values()) {
- profiles[i++] = group.getLocalProfile();
+ if (isCallerRadio || isCallerCreator(group)) {
+ profiles.add(group.getLocalProfile());
+ }
}
- return profiles;
+ return profiles.toArray(new SipProfile[profiles.size()]);
}
public void open(SipProfile localProfile) {
localProfile.setCallingUid(Binder.getCallingUid());
- if (localProfile.getAutoRegistration()) {
+ if (localProfile.getAutoRegistration() && isCallerRadio()) {
openToReceiveCalls(localProfile);
} else {
openToMakeCalls(localProfile);
@@ -153,8 +157,14 @@ public final class SipService extends ISipService.Stub {
String incomingCallBroadcastAction, ISipSessionListener listener) {
localProfile.setCallingUid(Binder.getCallingUid());
if (TextUtils.isEmpty(incomingCallBroadcastAction)) {
- throw new RuntimeException(
- "empty broadcast action for incoming call");
+ Log.w(TAG, "empty broadcast action for incoming call");
+ return;
+ }
+ if (incomingCallBroadcastAction.equals(
+ SipManager.ACTION_SIP_INCOMING_CALL) && !isCallerRadio()) {
+ Log.w(TAG, "failed to open the profile; "
+ + "the action string is reserved");
+ return;
}
if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": "
+ incomingCallBroadcastAction + ": " + listener);
@@ -171,29 +181,64 @@ public final class SipService extends ISipService.Stub {
}
}
+ private boolean isCallerCreator(SipSessionGroupExt group) {
+ SipProfile profile = group.getLocalProfile();
+ return (profile.getCallingUid() == Binder.getCallingUid());
+ }
+
+ private boolean isCallerCreatorOrRadio(SipSessionGroupExt group) {
+ return (isCallerRadio() || isCallerCreator(group));
+ }
+
+ private boolean isCallerRadio() {
+ return (Binder.getCallingUid() == Process.PHONE_UID);
+ }
+
public synchronized void close(String localProfileUri) {
- SipSessionGroupExt group = mSipGroups.remove(localProfileUri);
- if (group != null) {
- notifyProfileRemoved(group.getLocalProfile());
- group.close();
- if (isWifiOn() && !anyOpened()) releaseWifiLock();
+ SipSessionGroupExt group = mSipGroups.get(localProfileUri);
+ if (group == null) return;
+ if (!isCallerCreatorOrRadio(group)) {
+ Log.d(TAG, "only creator or radio can close this profile");
+ return;
}
+
+ group = mSipGroups.remove(localProfileUri);
+ notifyProfileRemoved(group.getLocalProfile());
+ group.close();
+ if (isWifiOn() && !anyOpened()) releaseWifiLock();
}
public synchronized boolean isOpened(String localProfileUri) {
SipSessionGroupExt group = mSipGroups.get(localProfileUri);
- return ((group != null) ? group.isOpened() : false);
+ if (group == null) return false;
+ if (isCallerCreatorOrRadio(group)) {
+ return group.isOpened();
+ } else {
+ Log.i(TAG, "only creator or radio can query on the profile");
+ return false;
+ }
}
public synchronized boolean isRegistered(String localProfileUri) {
SipSessionGroupExt group = mSipGroups.get(localProfileUri);
- return ((group != null) ? group.isRegistered() : false);
+ if (group == null) return false;
+ if (isCallerCreatorOrRadio(group)) {
+ return group.isRegistered();
+ } else {
+ Log.i(TAG, "only creator or radio can query on the profile");
+ return false;
+ }
}
public synchronized void setRegistrationListener(String localProfileUri,
ISipSessionListener listener) {
SipSessionGroupExt group = mSipGroups.get(localProfileUri);
- if (group != null) group.setListener(listener);
+ if (group == null) return;
+ if (isCallerCreator(group)) {
+ group.setListener(listener);
+ } else {
+ Log.i(TAG, "only creator can set listener on the profile");
+ }
}
public synchronized ISipSession createSession(SipProfile localProfile,
@@ -234,6 +279,8 @@ public final class SipService extends ISipService.Stub {
group = new SipSessionGroupExt(localProfile, null, null);
mSipGroups.put(key, group);
notifyProfileAdded(localProfile);
+ } else if (!isCallerCreator(group)) {
+ throw new SipException("only creator can access the profile");
}
return group;
}
@@ -244,6 +291,9 @@ public final class SipService extends ISipService.Stub {
String key = localProfile.getUriString();
SipSessionGroupExt group = mSipGroups.get(key);
if (group != null) {
+ if (!isCallerCreator(group)) {
+ throw new SipException("only creator can access the profile");
+ }
group.setIncomingCallBroadcastAction(
incomingCallBroadcastAction);
group.setListener(listener);
@@ -510,31 +560,43 @@ public final class SipService extends ISipService.Stub {
}
}
+ // KeepAliveProcess is controlled by AutoRegistrationProcess.
+ // All methods will be invoked in sync with SipService.this except realRun()
private class KeepAliveProcess implements Runnable {
private static final String TAG = "\\KEEPALIVE/";
private static final int INTERVAL = 10;
private SipSessionGroup.SipSessionImpl mSession;
+ private boolean mRunning = false;
public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) {
mSession = session;
}
public void start() {
+ if (mRunning) return;
+ mRunning = true;
mTimer.set(INTERVAL * 1000, this);
}
+ // timeout handler
public void run() {
+ if (!mRunning) return;
+ final SipSessionGroup.SipSessionImpl session = mSession;
+
// delegate to mExecutor
getExecutor().addTask(new Runnable() {
public void run() {
- realRun();
+ realRun(session);
}
});
}
- private void realRun() {
+ // real timeout handler
+ private void realRun(SipSessionGroup.SipSessionImpl session) {
synchronized (SipService.this) {
- SipSessionGroup.SipSessionImpl session = mSession.duplicate();
+ if (notCurrentSession(session)) return;
+
+ session = session.duplicate();
if (DEBUG) Log.d(TAG, "~~~ keepalive");
mTimer.cancel(this);
session.sendKeepAlive();
@@ -547,8 +609,14 @@ public final class SipService extends ISipService.Stub {
}
public void stop() {
+ mRunning = false;
+ mSession = null;
mTimer.cancel(this);
}
+
+ private boolean notCurrentSession(ISipSession session) {
+ return (session != mSession) || !mRunning;
+ }
}
private class AutoRegistrationProcess extends SipSessionAdapter
@@ -561,13 +629,15 @@ public final class SipService extends ISipService.Stub {
private long mExpiryTime;
private int mErrorCode;
private String mErrorMessage;
+ private boolean mRunning = false;
private String getAction() {
return toString();
}
public void start(SipSessionGroup group) {
- if (mSession == null) {
+ if (!mRunning) {
+ mRunning = true;
mBackoff = 1;
mSession = (SipSessionGroup.SipSessionImpl)
group.createSession(this);
@@ -584,35 +654,24 @@ public final class SipService extends ISipService.Stub {
}
public void stop() {
- stop(false);
- }
-
- private void stopButKeepStates() {
- stop(true);
- }
-
- private void stop(boolean keepStates) {
- if (mSession == null) return;
+ if (!mRunning) return;
+ mRunning = false;
+ mSession.setListener(null);
if (mConnected && mRegistered) mSession.unregister();
+
mTimer.cancel(this);
if (mKeepAliveProcess != null) {
mKeepAliveProcess.stop();
mKeepAliveProcess = null;
}
- if (!keepStates) {
- mSession = null;
- mRegistered = false;
- }
- }
- private boolean isStopped() {
- return (mSession == null);
+ mRegistered = false;
+ setListener(mProxy.getListener());
}
public void setListener(ISipSessionListener listener) {
synchronized (SipService.this) {
mProxy.setListener(listener);
- if (mSession == null) return;
try {
int state = (mSession == null)
@@ -632,6 +691,18 @@ public final class SipService extends ISipService.Stub {
mProxy.onRegistrationFailed(mSession, mErrorCode,
mErrorMessage);
}
+ } else if (!mConnected) {
+ mProxy.onRegistrationFailed(mSession,
+ SipErrorCode.DATA_CONNECTION_LOST,
+ "no data connection");
+ } else if (!mRunning) {
+ mProxy.onRegistrationFailed(mSession,
+ SipErrorCode.CLIENT_ERROR,
+ "registration not running");
+ } else {
+ mProxy.onRegistrationFailed(mSession,
+ SipErrorCode.IN_PROGRESS,
+ String.valueOf(state));
}
} catch (Throwable t) {
Log.w(TAG, "setListener(): " + t);
@@ -643,21 +714,29 @@ public final class SipService extends ISipService.Stub {
return mRegistered;
}
+ // timeout handler
public void run() {
- // delegate to mExecutor
- getExecutor().addTask(new Runnable() {
- public void run() {
- realRun();
- }
- });
+ synchronized (SipService.this) {
+ if (!mRunning) return;
+ final SipSessionGroup.SipSessionImpl session = mSession;
+
+ // delegate to mExecutor
+ getExecutor().addTask(new Runnable() {
+ public void run() {
+ realRun(session);
+ }
+ });
+ }
}
- private void realRun() {
- mErrorCode = SipErrorCode.NO_ERROR;
- mErrorMessage = null;
- if (DEBUG) Log.d(TAG, "~~~ registering");
+ // real timeout handler
+ private void realRun(SipSessionGroup.SipSessionImpl session) {
synchronized (SipService.this) {
- if (mConnected && !isStopped()) mSession.register(EXPIRY_TIME);
+ if (notCurrentSession(session)) return;
+ mErrorCode = SipErrorCode.NO_ERROR;
+ mErrorMessage = null;
+ if (DEBUG) Log.d(TAG, "~~~ registering");
+ if (mConnected) session.register(EXPIRY_TIME);
}
}
@@ -697,22 +776,29 @@ public final class SipService extends ISipService.Stub {
public void onRegistering(ISipSession session) {
if (DEBUG) Log.d(TAG, "onRegistering(): " + session);
synchronized (SipService.this) {
- if (!isStopped() && (session != mSession)) return;
+ if (notCurrentSession(session)) return;
+
mRegistered = false;
mProxy.onRegistering(session);
}
}
+ private boolean notCurrentSession(ISipSession session) {
+ if (session != mSession) {
+ ((SipSessionGroup.SipSessionImpl) session).setListener(null);
+ return true;
+ }
+ return !mRunning;
+ }
+
@Override
public void onRegistrationDone(ISipSession session, int duration) {
if (DEBUG) Log.d(TAG, "onRegistrationDone(): " + session);
synchronized (SipService.this) {
- if (!isStopped() && (session != mSession)) return;
+ if (notCurrentSession(session)) return;
mProxy.onRegistrationDone(session, duration);
- if (isStopped()) return;
-
if (duration > 0) {
mSession.clearReRegisterRequired();
mExpiryTime = SystemClock.elapsedRealtime()
@@ -751,17 +837,18 @@ public final class SipService extends ISipService.Stub {
if (DEBUG) Log.d(TAG, "onRegistrationFailed(): " + session + ": "
+ SipErrorCode.toString(errorCode) + ": " + message);
synchronized (SipService.this) {
- if (!isStopped() && (session != mSession)) return;
- mErrorCode = errorCode;
- mErrorMessage = message;
- mProxy.onRegistrationFailed(session, errorCode, message);
+ if (notCurrentSession(session)) return;
if (errorCode == SipErrorCode.INVALID_CREDENTIALS) {
if (DEBUG) Log.d(TAG, " pause auto-registration");
- stopButKeepStates();
- } else if (!isStopped()) {
+ stop();
+ } else {
onError();
}
+
+ mErrorCode = errorCode;
+ mErrorMessage = message;
+ mProxy.onRegistrationFailed(session, errorCode, message);
}
}
@@ -769,14 +856,11 @@ public final class SipService extends ISipService.Stub {
public void onRegistrationTimeout(ISipSession session) {
if (DEBUG) Log.d(TAG, "onRegistrationTimeout(): " + session);
synchronized (SipService.this) {
- if (!isStopped() && (session != mSession)) return;
+ if (notCurrentSession(session)) return;
+
mErrorCode = SipErrorCode.TIME_OUT;
mProxy.onRegistrationTimeout(session);
-
- if (!isStopped()) {
- mRegistered = false;
- onError();
- }
+ onError();
}
}
@@ -883,6 +967,7 @@ public final class SipService extends ISipService.Stub {
mConnected = connected;
}
+ // timeout handler
@Override
public void run() {
// delegate to mExecutor
diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java
index 4321d7b..c68fa1b 100644
--- a/voip/java/com/android/server/sip/SipSessionGroup.java
+++ b/voip/java/com/android/server/sip/SipSessionGroup.java
@@ -334,12 +334,12 @@ class SipSessionGroup implements SipListener {
if (isRequestEvent(Request.INVITE, evt)) {
RequestEvent event = (RequestEvent) evt;
SipSessionImpl newSession = new SipSessionImpl(mProxy);
+ newSession.mState = SipSession.State.INCOMING_CALL;
newSession.mServerTransaction = mSipHelper.sendRinging(event,
generateTag());
newSession.mDialog = newSession.mServerTransaction.getDialog();
newSession.mInviteReceived = event;
newSession.mPeerProfile = createPeerProfile(event.getRequest());
- newSession.mState = SipSession.State.INCOMING_CALL;
newSession.mPeerSessionDescription =
extractContent(event.getRequest());
addSipSession(newSession);
@@ -708,7 +708,6 @@ class SipSessionGroup implements SipListener {
case SipSession.State.PINGING:
reset();
mReRegisterFlag = true;
- mState = SipSession.State.READY_TO_CALL;
break;
default:
@@ -877,6 +876,7 @@ class SipSessionGroup implements SipListener {
private boolean readyForCall(EventObject evt) throws SipException {
// expect MakeCallCommand, RegisterCommand, DEREGISTER
if (evt instanceof MakeCallCommand) {
+ mState = SipSession.State.OUTGOING_CALL;
MakeCallCommand cmd = (MakeCallCommand) evt;
mPeerProfile = cmd.getPeerProfile();
mClientTransaction = mSipHelper.sendInvite(mLocalProfile,
@@ -884,25 +884,24 @@ class SipSessionGroup implements SipListener {
generateTag());
mDialog = mClientTransaction.getDialog();
addSipSession(this);
- mState = SipSession.State.OUTGOING_CALL;
mProxy.onCalling(this);
startSessionTimer(cmd.getTimeout());
return true;
} else if (evt instanceof RegisterCommand) {
+ mState = SipSession.State.REGISTERING;
int duration = ((RegisterCommand) evt).getDuration();
mClientTransaction = mSipHelper.sendRegister(mLocalProfile,
generateTag(), duration);
mDialog = mClientTransaction.getDialog();
addSipSession(this);
- mState = SipSession.State.REGISTERING;
mProxy.onRegistering(this);
return true;
} else if (DEREGISTER == evt) {
+ mState = SipSession.State.DEREGISTERING;
mClientTransaction = mSipHelper.sendRegister(mLocalProfile,
generateTag(), 0);
mDialog = mClientTransaction.getDialog();
addSipSession(this);
- mState = SipSession.State.DEREGISTERING;
mProxy.onRegistering(this);
return true;
}
@@ -913,11 +912,11 @@ class SipSessionGroup implements SipListener {
// expect MakeCallCommand(answering) , END_CALL cmd , Cancel
if (evt instanceof MakeCallCommand) {
// answer call
+ mState = SipSession.State.INCOMING_CALL_ANSWERING;
mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived,
mLocalProfile,
((MakeCallCommand) evt).getSessionDescription(),
mServerTransaction);
- mState = SipSession.State.INCOMING_CALL_ANSWERING;
startSessionTimer(((MakeCallCommand) evt).getTimeout());
return true;
} else if (END_CALL == evt) {
@@ -1009,8 +1008,8 @@ class SipSessionGroup implements SipListener {
// RFC says that UA should not send out cancel when no
// response comes back yet. We are cheating for not checking
// response.
- mSipHelper.sendCancel(mClientTransaction);
mState = SipSession.State.OUTGOING_CALL_CANCELING;
+ mSipHelper.sendCancel(mClientTransaction);
startSessionTimer(CANCEL_CALL_TIMER);
return true;
}
@@ -1065,8 +1064,8 @@ class SipSessionGroup implements SipListener {
return true;
} else if (isRequestEvent(Request.INVITE, evt)) {
// got Re-INVITE
- RequestEvent event = mInviteReceived = (RequestEvent) evt;
mState = SipSession.State.INCOMING_CALL;
+ RequestEvent event = mInviteReceived = (RequestEvent) evt;
mPeerSessionDescription = extractContent(event.getRequest());
mServerTransaction = null;
mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription);
@@ -1077,9 +1076,9 @@ class SipSessionGroup implements SipListener {
return true;
} else if (evt instanceof MakeCallCommand) {
// to change call
+ mState = SipSession.State.OUTGOING_CALL;
mClientTransaction = mSipHelper.sendReinvite(mDialog,
((MakeCallCommand) evt).getSessionDescription());
- mState = SipSession.State.OUTGOING_CALL;
startSessionTimer(((MakeCallCommand) evt).getTimeout());
return true;
}
diff --git a/voip/jni/rtp/AmrCodec.cpp b/voip/jni/rtp/AmrCodec.cpp
index 9a2227d..f3ecac2 100644
--- a/voip/jni/rtp/AmrCodec.cpp
+++ b/voip/jni/rtp/AmrCodec.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include <string.h>
+
#include "AudioCodec.h"
#include "gsmamr_dec.h"
@@ -21,6 +23,170 @@
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, 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.
+ 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, 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:
@@ -91,6 +257,11 @@ int GsmEfrCodec::decode(int16_t *samples, void *payload, int length)
} // namespace
+AudioCodec *newAmrCodec()
+{
+ return new AmrCodec;
+}
+
AudioCodec *newGsmEfrCodec()
{
return new GsmEfrCodec;
diff --git a/voip/jni/rtp/AudioCodec.cpp b/voip/jni/rtp/AudioCodec.cpp
index afc193c..2267ea0 100644
--- a/voip/jni/rtp/AudioCodec.cpp
+++ b/voip/jni/rtp/AudioCodec.cpp
@@ -21,6 +21,7 @@
extern AudioCodec *newAlawCodec();
extern AudioCodec *newUlawCodec();
extern AudioCodec *newGsmCodec();
+extern AudioCodec *newAmrCodec();
extern AudioCodec *newGsmEfrCodec();
struct AudioCodecType {
@@ -30,6 +31,7 @@ struct AudioCodecType {
{"PCMA", newAlawCodec},
{"PCMU", newUlawCodec},
{"GSM", newGsmCodec},
+ {"AMR", newAmrCodec},
{"GSM-EFR", newGsmEfrCodec},
{NULL, NULL},
};
diff --git a/voip/jni/rtp/AudioGroup.cpp b/voip/jni/rtp/AudioGroup.cpp
index f09edb6..a953d38 100644
--- a/voip/jni/rtp/AudioGroup.cpp
+++ b/voip/jni/rtp/AudioGroup.cpp
@@ -57,9 +57,9 @@ int gRandom = -1;
// a modulo operation on the index while accessing the array. However modulo can
// be expensive on some platforms, such as ARM. Thus we round up the size of the
// array to the nearest power of 2 and then use bitwise-and instead of modulo.
-// Currently we make it 256ms long and assume packet interval is 32ms or less.
-// The first 64ms is the place where samples get mixed. The rest 192ms is the
-// real jitter buffer. For a stream at 8000Hz it takes 4096 bytes. These numbers
+// Currently we make it 512ms long and assume packet interval is 40ms or less.
+// The first 80ms is the place where samples get mixed. The rest 432ms is the
+// real jitter buffer. For a stream at 8000Hz it takes 8192 bytes. These numbers
// are chosen by experiments and each of them can be adjusted as needed.
// Other notes:
@@ -69,7 +69,11 @@ int gRandom = -1;
// milliseconds. No floating points.
// + If we cannot get enough CPU, we drop samples and simulate packet loss.
// + Resampling is not done yet, so streams in one group must use the same rate.
-// For the first release we might only support 8kHz and 16kHz.
+// For the first release only 8000Hz is supported.
+
+#define BUFFER_SIZE 512
+#define HISTORY_SIZE 80
+#define MEASURE_PERIOD 2000
class AudioStream
{
@@ -85,7 +89,6 @@ public:
void encode(int tick, AudioStream *chain);
void decode(int tick);
-private:
enum {
NORMAL = 0,
SEND_ONLY = 1,
@@ -93,6 +96,7 @@ private:
LAST_MODE = 2,
};
+private:
int mMode;
int mSocket;
sockaddr_storage mRemote;
@@ -111,6 +115,7 @@ private:
int mBufferMask;
int mBufferHead;
int mBufferTail;
+ int mLatencyTimer;
int mLatencyScore;
uint16_t mSequence;
@@ -159,12 +164,13 @@ bool AudioStream::set(int mode, int socket, sockaddr_storage *remote,
mInterval = mSampleCount / mSampleRate;
// Allocate jitter buffer.
- for (mBufferMask = 8192; mBufferMask < sampleRate; mBufferMask <<= 1);
- mBufferMask >>= 2;
+ for (mBufferMask = 8; mBufferMask < mSampleRate; mBufferMask <<= 1);
+ mBufferMask *= BUFFER_SIZE;
mBuffer = new int16_t[mBufferMask];
--mBufferMask;
mBufferHead = 0;
mBufferTail = 0;
+ mLatencyTimer = 0;
mLatencyScore = 0;
// Initialize random bits.
@@ -196,8 +202,8 @@ bool AudioStream::set(int mode, int socket, sockaddr_storage *remote,
}
}
- LOGD("stream[%d] is configured as %s %dkHz %dms", mSocket,
- (codec ? codec->name : "RAW"), mSampleRate, mInterval);
+ LOGD("stream[%d] is configured as %s %dkHz %dms mode %d", mSocket,
+ (codec ? codec->name : "RAW"), mSampleRate, mInterval, mMode);
return true;
}
@@ -247,7 +253,7 @@ void AudioStream::encode(int tick, AudioStream *chain)
mTick += skipped * mInterval;
mSequence += skipped;
mTimestamp += skipped * mSampleCount;
- LOGD("stream[%d] skips %d packets", mSocket, skipped);
+ LOGV("stream[%d] skips %d packets", mSocket, skipped);
}
tick = mTick;
@@ -296,7 +302,7 @@ void AudioStream::encode(int tick, AudioStream *chain)
if (!mixed) {
if ((mTick ^ mLogThrottle) >> 10) {
mLogThrottle = mTick;
- LOGD("stream[%d] no data", mSocket);
+ LOGV("stream[%d] no data", mSocket);
}
return;
}
@@ -324,7 +330,7 @@ void AudioStream::encode(int tick, AudioStream *chain)
buffer[2] = mSsrc;
int length = mCodec->encode(&buffer[3], samples);
if (length <= 0) {
- LOGD("stream[%d] encoder error", mSocket);
+ LOGV("stream[%d] encoder error", mSocket);
return;
}
sendto(mSocket, buffer, length + 12, MSG_DONTWAIT, (sockaddr *)&mRemote,
@@ -340,31 +346,37 @@ void AudioStream::decode(int tick)
}
// Make sure mBufferHead and mBufferTail are reasonable.
- if ((unsigned int)(tick + 256 - mBufferHead) > 1024) {
- mBufferHead = tick - 64;
+ if ((unsigned int)(tick + BUFFER_SIZE - mBufferHead) > BUFFER_SIZE * 2) {
+ mBufferHead = tick - HISTORY_SIZE;
mBufferTail = mBufferHead;
}
- if (tick - mBufferHead > 64) {
+ if (tick - mBufferHead > HISTORY_SIZE) {
// Throw away outdated samples.
- mBufferHead = tick - 64;
+ mBufferHead = tick - HISTORY_SIZE;
if (mBufferTail - mBufferHead < 0) {
mBufferTail = mBufferHead;
}
}
- if (mBufferTail - tick <= 80) {
- mLatencyScore = tick;
- } else if (tick - mLatencyScore >= 5000) {
- // Reset the jitter buffer to 40ms if the latency keeps larger than 80ms
- // in the past 5s. This rarely happens, so let us just keep it simple.
- LOGD("stream[%d] latency control", mSocket);
- mBufferTail = tick + 40;
+ // Adjust the jitter buffer if the latency keeps larger than two times of the
+ // packet interval in the past two seconds.
+ int score = mBufferTail - tick - mInterval * 2;
+ if (mLatencyScore > score) {
+ mLatencyScore = score;
+ }
+ if (mLatencyScore <= 0) {
+ mLatencyTimer = tick;
+ mLatencyScore = score;
+ } else if (tick - mLatencyTimer >= MEASURE_PERIOD) {
+ LOGV("stream[%d] reduces latency of %dms", mSocket, mLatencyScore);
+ mBufferTail -= mLatencyScore;
+ mLatencyTimer = tick;
}
- if (mBufferTail - mBufferHead > 256 - mInterval) {
+ if (mBufferTail - mBufferHead > BUFFER_SIZE - mInterval) {
// Buffer overflow. Drop the packet.
- LOGD("stream[%d] buffer overflow", mSocket);
+ LOGV("stream[%d] buffer overflow", mSocket);
recv(mSocket, &c, 1, MSG_DONTWAIT);
return;
}
@@ -388,7 +400,7 @@ void AudioStream::decode(int tick)
// reliable but at least they can be used to identify duplicates?
if (length < 12 || length > (int)sizeof(buffer) ||
(ntohl(*(uint32_t *)buffer) & 0xC07F0000) != mCodecMagic) {
- LOGD("stream[%d] malformed packet", mSocket);
+ LOGV("stream[%d] malformed packet", mSocket);
return;
}
int offset = 12 + ((buffer[0] & 0x0F) << 2);
@@ -408,22 +420,22 @@ void AudioStream::decode(int tick)
}
}
if (length <= 0) {
- LOGD("stream[%d] decoder error", mSocket);
+ LOGV("stream[%d] decoder error", mSocket);
return;
}
if (tick - mBufferTail > 0) {
- // Buffer underrun. Reset the jitter buffer to 40ms.
- LOGD("stream[%d] buffer underrun", mSocket);
+ // Buffer underrun. Reset the jitter buffer.
+ LOGV("stream[%d] buffer underrun", mSocket);
if (mBufferTail - mBufferHead <= 0) {
- mBufferHead = tick + 40;
+ mBufferHead = tick + mInterval;
mBufferTail = mBufferHead;
} else {
- int tail = (tick + 40) * mSampleRate;
+ int tail = (tick + mInterval) * mSampleRate;
for (int i = mBufferTail * mSampleRate; i - tail < 0; ++i) {
mBuffer[i & mBufferMask] = 0;
}
- mBufferTail = tick + 40;
+ mBufferTail = tick + mInterval;
}
}
@@ -450,7 +462,6 @@ public:
bool add(AudioStream *stream);
bool remove(int socket);
-private:
enum {
ON_HOLD = 0,
MUTED = 1,
@@ -459,6 +470,7 @@ private:
LAST_MODE = 3,
};
+private:
AudioStream *mChain;
int mEventQueue;
volatile int mDtmfEvent;
@@ -680,7 +692,7 @@ bool AudioGroup::NetworkThread::threadLoop()
int count = 0;
for (AudioStream *stream = chain; stream; stream = stream->mNext) {
- if (!stream->mTick || tick - stream->mTick >= 0) {
+ if (tick - stream->mTick >= 0) {
stream->encode(tick, chain);
}
if (deadline - stream->mTick > 0) {
@@ -934,6 +946,10 @@ void remove(JNIEnv *env, jobject thiz, jint socket)
void setMode(JNIEnv *env, jobject thiz, jint mode)
{
+ if (mode < 0 || mode > AudioGroup::LAST_MODE) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative);
if (group && !group->setMode(mode)) {
jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
diff --git a/voip/jni/rtp/G711Codec.cpp b/voip/jni/rtp/G711Codec.cpp
index 091afa9..a467acf 100644
--- a/voip/jni/rtp/G711Codec.cpp
+++ b/voip/jni/rtp/G711Codec.cpp
@@ -18,7 +18,7 @@
namespace {
-int8_t gExponents[128] = {
+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,
diff --git a/voip/jni/rtp/RtpStream.cpp b/voip/jni/rtp/RtpStream.cpp
index 33b88e4..f5efc17 100644
--- a/voip/jni/rtp/RtpStream.cpp
+++ b/voip/jni/rtp/RtpStream.cpp
@@ -88,13 +88,11 @@ jint create(JNIEnv *env, jobject thiz, jstring jAddress)
jint dup(JNIEnv *env, jobject thiz)
{
- int socket1 = env->GetIntField(thiz, gNative);
- int socket2 = ::dup(socket1);
- if (socket2 == -1) {
+ int socket = ::dup(env->GetIntField(thiz, gNative));
+ if (socket == -1) {
jniThrowException(env, "java/lang/IllegalStateException", strerror(errno));
}
- LOGD("dup %d to %d", socket1, socket2);
- return socket2;
+ return socket;
}
void close(JNIEnv *env, jobject thiz)
@@ -102,7 +100,6 @@ void close(JNIEnv *env, jobject thiz)
int socket = env->GetIntField(thiz, gNative);
::close(socket);
env->SetIntField(thiz, gNative, -1);
- LOGD("close %d", socket);
}
JNINativeMethod gMethods[] = {