diff options
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=""error_code"" + value=""_id"" 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=""_id"" + value=""last_modified_timestamp"" 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=""last_modified_timestamp"" + value=""local_uri"" 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=""local_uri"" + value=""media_type"" 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=""media_type"" + value=""reason"" 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 Binary files differindex f8e40ec..e8c1aa3 100644 --- a/core/res/res/drawable-hdpi/overscroll_edge.png +++ b/core/res/res/drawable-hdpi/overscroll_edge.png diff --git a/core/res/res/drawable-hdpi/overscroll_glow.png b/core/res/res/drawable-hdpi/overscroll_glow.png Binary files differindex a8a62c4..3418384 100644 --- a/core/res/res/drawable-hdpi/overscroll_glow.png +++ b/core/res/res/drawable-hdpi/overscroll_glow.png 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 Binary files differindex 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 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 Binary files differindex 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 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[] = { |