Merge "Import revised translations.  DO NOT MERGE" into gingerbread
diff --git a/api/current.xml b/api/current.xml
index a37a533..eab9c7f 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -24605,17 +24605,6 @@
  visibility="public"
 >
 </field>
-<field name="COLUMN_ERROR_CODE"
- type="java.lang.String"
- transient="false"
- volatile="false"
- value="&quot;error_code&quot;"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
 <field name="COLUMN_ID"
  type="java.lang.String"
  transient="false"
@@ -24660,6 +24649,17 @@
  visibility="public"
 >
 </field>
+<field name="COLUMN_REASON"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;reason&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="COLUMN_STATUS"
  type="java.lang.String"
  transient="false"
@@ -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 @@
     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 @@
 #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 @@
             }
             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 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 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 @@
         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 @@
     };
 
     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 @@
                     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 @@
             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 @@
             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 @@
             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 @@
                 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 @@
          * value should be 90.
          *
          * @see #setDisplayOrientation(int)
+         * @see #setRotation(int)
          */
         public int orientation;
     };
@@ -1716,23 +1717,46 @@
         }
 
         /**
-         * 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.
+         * 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).
          *
-         * 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.
          *
-         * @param rotation The orientation of the device in degrees. Rotation
-         *                 can only be 0, 90, 180 or 270.
+         * 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 @@
     }
 
     /**
-     * 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 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 @@
         }
 
         /**
-         * 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 static final int STATUS_PENDING = 190;
 
         /**
-         * This download hasn't stated yet and is paused
-         */
-        public static final int STATUS_PENDING_PAUSED = 191;
-
-        /**
          * This download has started
          */
         public static final int STATUS_RUNNING = 192;
 
         /**
-         * This download has started and is paused
+         * This download has been paused by the owning app.
          */
-        public static final int STATUS_RUNNING_PAUSED = 193;
+        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 exceeded a size limit for mobile networks and is waiting for a Wi-Fi
+         * connection to proceed.
+         */
+        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 @@
     private boolean mOutsideTouchable = false;
     private boolean mClippingEnabled = true;
     private boolean mSplitTouchEnabled;
+    private boolean mLayoutInScreen;
 
     private OnTouchListener mTouchInterceptor;
     
@@ -596,6 +597,29 @@
     }
 
     /**
+     * <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 @@
                 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 @@
         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 @@
         if (mError != null) {
             hideError();
         }
+
+        hideControllers();
     }
 
     @Override
@@ -4118,6 +4120,15 @@
         */
 
         canvas.restore();
+
+        if (mInsertionPointCursorController != null &&
+                mInsertionPointCursorController.isShowing()) {
+            mInsertionPointCursorController.updatePosition();
+        }
+        if (mSelectionModifierCursorController != null &&
+                mSelectionModifierCursorController.isShowing()) {
+            mSelectionModifierCursorController.updatePosition();
+        }
     }
 
     @Override
@@ -4736,6 +4747,7 @@
         if (mInputMethodState != null) {
             mInputMethodState.mExtracting = req;
         }
+        hideControllers();
     }
     
     /**
@@ -6266,7 +6278,11 @@
         
         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 @@
             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 @@
 
                 if (hasSelection()) {
                     startTextSelectionMode();
-                } else if (mInsertionPointCursorController != null) {
-                    mInsertionPointCursorController.show();
                 }
             }
         }
@@ -7645,6 +7668,8 @@
         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 @@
             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 @@
             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 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 @@
                 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 @@
         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/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index 6e11cff..2a8cd94 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -180,7 +180,7 @@
                 Log.d(TAG, "Found gdbserver: " + entry.getName());
             }
 
-            final String installGdbServerPath = APK_LIB + GDBSERVER;
+            final String installGdbServerPath = GDBSERVER;
             nativeFiles.add(Pair.create(entry, installGdbServerPath));
 
             return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES;
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 @@
 
         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 @@
         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 @@
 
             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 @@
         // 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 @@
 
             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.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 @@
         }
     }
 
-    //==============================================================================================
-    // 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 @@
     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/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java
index 70d4d6a..d4491d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java
@@ -20,6 +20,8 @@
 import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.util.Slog;
+import android.view.View;
 import android.widget.ImageView;
 import android.widget.RemoteViews.RemoteView;
 
@@ -43,7 +45,7 @@
         }
         if (drawable instanceof AnimationDrawable) {
             mAnim = (AnimationDrawable)drawable;
-            if (mAttached) {
+            if (isShown()) {
                 mAnim.start();
             }
         } else {
@@ -67,9 +69,6 @@
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
-        if (mAnim != null) {
-            mAnim.start();
-        }
         mAttached = true;
     }
 
@@ -81,5 +80,17 @@
         }
         mAttached = false;
     }
+
+    @Override
+    protected void onVisibilityChanged(View changedView, int vis) {
+        super.onVisibilityChanged(changedView, vis);
+        if (mAnim != null) {
+            if (isShown()) {
+                mAnim.start();
+            } else {
+                mAnim.stop();
+            }
+        }
+    }
 }
 
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_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/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 643b2f5..361cd3b 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -58,6 +58,7 @@
 import android.util.Slog;
 import android.util.PrintWriterPrinter;
 
+import com.android.internal.content.PackageMonitor;
 import com.android.internal.location.GpsNetInitiatedHandler;
 
 import com.android.server.location.GeocoderProxy;
@@ -116,13 +117,15 @@
     private static boolean sProvidersLoaded = false;
 
     private final Context mContext;
+    private final String mNetworkLocationProviderPackageName;
+    private final String mGeocodeProviderPackageName;
     private GeocoderProxy mGeocodeProvider;
     private IGpsStatusProvider mGpsStatusProvider;
     private INetInitiatedListener mNetInitiatedListener;
     private LocationWorkerHandler mLocationHandler;
 
     // Cache the real providers for use in addTestProvider() and removeTestProvider()
-     LocationProviderInterface mNetworkLocationProvider;
+     LocationProviderProxy mNetworkLocationProvider;
      LocationProviderInterface mGpsLocationProvider;
 
     // Handler messages
@@ -472,19 +475,15 @@
         mEnabledProviders.add(passiveProvider.getName());
 
         // initialize external network location and geocoder services
-        Resources resources = mContext.getResources();
-        String serviceName = resources.getString(
-                com.android.internal.R.string.config_networkLocationProvider);
-        if (serviceName != null) {
+        if (mNetworkLocationProviderPackageName != null) {
             mNetworkLocationProvider =
                 new LocationProviderProxy(mContext, LocationManager.NETWORK_PROVIDER,
-                        serviceName, mLocationHandler);
+                        mNetworkLocationProviderPackageName, mLocationHandler);
             addProvider(mNetworkLocationProvider);
         }
 
-        serviceName = resources.getString(com.android.internal.R.string.config_geocodeProvider);
-        if (serviceName != null) {
-            mGeocodeProvider = new GeocoderProxy(mContext, serviceName);
+        if (mGeocodeProviderPackageName != null) {
+            mGeocodeProvider = new GeocoderProxy(mContext, mGeocodeProviderPackageName);
         }
 
         updateProvidersLocked();
@@ -496,6 +495,12 @@
     public LocationManagerService(Context context) {
         super();
         mContext = context;
+        Resources resources = context.getResources();
+        mNetworkLocationProviderPackageName = resources.getString(
+                com.android.internal.R.string.config_networkLocationProvider);
+        mGeocodeProviderPackageName = resources.getString(
+                com.android.internal.R.string.config_geocodeProvider);
+        mPackageMonitor.register(context, true);
 
         if (LOCAL_LOGV) {
             Slog.v(TAG, "Constructed LocationManager Service");
@@ -1920,6 +1925,23 @@
         }
     };
 
+    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+        @Override
+        public void onPackageUpdateFinished(String packageName, int uid) {
+            String packageDot = packageName + ".";
+
+            // reconnect to external providers after their packages have been updated
+            if (mNetworkLocationProvider != null &&
+                    mNetworkLocationProviderPackageName.startsWith(packageDot)) {
+                mNetworkLocationProvider.reconnect();
+            }
+            if (mGeocodeProvider != null &&
+                    mGeocodeProviderPackageName.startsWith(packageDot)) {
+                mGeocodeProvider.reconnect();
+            }
+        }
+    };
+
     // Wake locks
 
     private void incrementPendingBroadcasts() {
diff --git a/services/java/com/android/server/location/GeocoderProxy.java b/services/java/com/android/server/location/GeocoderProxy.java
index 3c05da2..d9b49fd 100644
--- a/services/java/com/android/server/location/GeocoderProxy.java
+++ b/services/java/com/android/server/location/GeocoderProxy.java
@@ -50,17 +50,24 @@
         mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
     }
 
+    public void reconnect() {
+        synchronized (mServiceConnection) {
+            mContext.unbindService(mServiceConnection);
+            mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
+        }
+    }
+
     private class Connection implements ServiceConnection {
         public void onServiceConnected(ComponentName className, IBinder service) {
             Log.d(TAG, "onServiceConnected " + className);
-            synchronized (this) {
+            synchronized (mServiceConnection) {
                 mProvider = IGeocodeProvider.Stub.asInterface(service);
             }
         }
 
         public void onServiceDisconnected(ComponentName className) {
             Log.d(TAG, "onServiceDisconnected " + className);
-            synchronized (this) {
+            synchronized (mServiceConnection) {
                 mProvider = null;
             }
         }
diff --git a/services/java/com/android/server/location/LocationProviderProxy.java b/services/java/com/android/server/location/LocationProviderProxy.java
index 7dc9920..ef2056b 100644
--- a/services/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/java/com/android/server/location/LocationProviderProxy.java
@@ -45,6 +45,7 @@
 
     private final Context mContext;
     private final String mName;
+    private final String mServiceName;
     private ILocationProvider mProvider;
     private Handler mHandler;
     private final Connection mServiceConnection = new Connection();
@@ -65,14 +66,24 @@
             Handler handler) {
         mContext = context;
         mName = name;
+        mServiceName = serviceName;
         mHandler = handler;
         mContext.bindService(new Intent(serviceName), mServiceConnection, Context.BIND_AUTO_CREATE);
     }
 
+    public void reconnect() {
+        synchronized (mServiceConnection) {
+            // unbind first
+            mContext.unbindService(mServiceConnection);
+            mContext.bindService(new Intent(mServiceName), mServiceConnection,
+                Context.BIND_AUTO_CREATE);
+        }
+    }
+
     private class Connection implements ServiceConnection {
         public void onServiceConnected(ComponentName className, IBinder service) {
             Log.d(TAG, "LocationProviderProxy.onServiceConnected " + className);
-            synchronized (this) {
+            synchronized (mServiceConnection) {
                 mProvider = ILocationProvider.Stub.asInterface(service);
                 if (mProvider != null) {
                     mHandler.post(mServiceConnectedTask);
@@ -82,7 +93,7 @@
 
         public void onServiceDisconnected(ComponentName className) {
             Log.d(TAG, "LocationProviderProxy.onServiceDisconnected " + className);
-            synchronized (this) {
+            synchronized (mServiceConnection) {
                 mProvider = null;
             }
         }
diff --git a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
index 798a5a5..25ca559 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
@@ -24,9 +24,10 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
+import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.PhoneLookup;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -36,7 +37,7 @@
 
 public class CallerInfoAsyncQuery {
 
-    private static final boolean DBG = false;
+    private static final boolean DBG = true; // STOPSHIP: disable debugging before ship
     private static final String LOG_TAG = "CallerInfoAsyncQuery";
 
     private static final int EVENT_NEW_QUERY = 1;
@@ -189,7 +190,7 @@
          */
         @Override
         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-            if (DBG) log("query complete for token: " + token);
+            if (DBG) log("##### onQueryComplete() #####   query complete for token: " + token);
 
             //get the cookie and notify the listener.
             CookieWrapper cw = (CookieWrapper) cookie;
@@ -227,6 +228,8 @@
                     mCallerInfo = new CallerInfo().markAsVoiceMail();
                 } else {
                     mCallerInfo = CallerInfo.getCallerInfo(mQueryContext, mQueryUri, cursor);
+                    if (DBG) log("==> Got mCallerInfo: " + mCallerInfo);
+
                     // Use the number entered by the user for display.
                     if (!TextUtils.isEmpty(cw.number)) {
                         mCallerInfo.phoneNumber = PhoneNumberUtils.formatNumber(cw.number);
@@ -238,7 +241,7 @@
                 //notify that we can clean up the queue after this.
                 CookieWrapper endMarker = new CookieWrapper();
                 endMarker.event = EVENT_END_OF_QUEUE;
-                startQuery (token, endMarker, null, null, null, null, null);
+                startQuery(token, endMarker, null, null, null, null, null);
             }
 
             //notify the listener that the query is complete.
@@ -274,24 +277,82 @@
         cw.cookie = cookie;
         cw.event = EVENT_NEW_QUERY;
 
-        c.mHandler.startQuery (token, cw, contactRef, null, null, null, null);
+        c.mHandler.startQuery(token, cw, contactRef, null, null, null, null);
 
         return c;
     }
 
     /**
-     * Factory method to start query with a number
+     * Factory method to start the query based on a number.
+     *
+     * Note: if the number contains an "@" character we treat it
+     * as a SIP address, and look it up directly in the Data table
+     * rather than using the PhoneLookup table.
+     * TODO: But eventually we should expose two separate methods, one for
+     * numbers and one for SIP addresses, and then have
+     * PhoneUtils.startGetCallerInfo() decide which one to call based on
+     * the phone type of the incoming connection.
      */
     public static CallerInfoAsyncQuery startQuery(int token, Context context, String number,
             OnQueryCompleteListener listener, Object cookie) {
-        //construct the URI object and start Query.
-        Uri contactRef = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
+        if (DBG) {
+            log("##### CallerInfoAsyncQuery startQuery()... #####");
+            log("- number: " + number);
+            log("- cookie: " + cookie);
+        }
+
+        // Construct the URI object and query params, and start the query.
+
+        Uri contactRef;
+        String selection;
+        String[] selectionArgs;
+
+        if (PhoneNumberUtils.isUriNumber(number)) {
+            // "number" is really a SIP address.
+            if (DBG) log("  - Treating number as a SIP address: " + number);
+
+            // We look up SIP addresses directly in the Data table:
+            contactRef = Data.CONTENT_URI;
+
+            // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
+            //
+            // Also note we use "upper(data1)" in the WHERE clause, and
+            // uppercase the incoming SIP address, in order to do a
+            // case-insensitive match.
+            //
+            // TODO: need to confirm that the use of upper() doesn't
+            // prevent us from using the index!  (Linear scan of the whole
+            // contacts DB can be very slow.)
+            //
+            // TODO: May also need to normalize by adding "sip:" as a
+            // prefix, if we start storing SIP addresses that way in the
+            // database.
+
+            selection = "upper(" + Data.DATA1 + ")=?"
+                    + " AND "
+                    + Data.MIMETYPE + "='" + SipAddress.CONTENT_ITEM_TYPE + "'";
+            selectionArgs = new String[] { number.toUpperCase() };
+
+        } else {
+            // "number" is a regular phone number.  Use the PhoneLookup table:
+            contactRef = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
+            selection = null;
+            selectionArgs = null;
+        }
+
+        if (DBG) {
+            log("==> contactRef: " + contactRef);
+            log("==> selection: " + selection);
+            if (selectionArgs != null) {
+                for (int i = 0; i < selectionArgs.length; i++) {
+                    log("==> selectionArgs[" + i + "]: " + selectionArgs[i]);
+                }
+            }
+        }
 
         CallerInfoAsyncQuery c = new CallerInfoAsyncQuery();
         c.allocate(context, contactRef);
 
-        if (DBG) log("starting query for number: " + number + " handler: " + c.toString());
-
         //create cookieWrapper, start query
         CookieWrapper cw = new CookieWrapper();
         cw.listener = listener;
@@ -307,10 +368,15 @@
             cw.event = EVENT_NEW_QUERY;
         }
 
-        c.mHandler.startQuery (token, cw, contactRef, null, null, null, null);
-
+        c.mHandler.startQuery(token,
+                              cw,  // cookie
+                              contactRef,  // uri
+                              null,  // projection
+                              selection,  // selection
+                              selectionArgs,  // selectionArgs
+                              null);  // orderBy
         return c;
-   }
+    }
 
     /**
      * Method to add listeners to a currently running query
@@ -326,7 +392,7 @@
         cw.cookie = cookie;
         cw.event = EVENT_ADD_LISTENER;
 
-        mHandler.startQuery (token, cw, null, null, null, null, null);
+        mHandler.startQuery(token, cw, null, null, null, null, null);
     }
 
     /**
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 @@
                 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 @@
     }
 
     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 @@
                 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 @@
                         }
                     }
                     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 @@
                 }
                 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 @@
             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 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 @@
             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 @@
             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 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 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 @@
     }
 
     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 @@
 
     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.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.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 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 @@
             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 @@
         }
     }
 
+    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 @@
             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 @@
         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 @@
         }
     }
 
+    // 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 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 @@
         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 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 @@
                             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 @@
             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 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 @@
             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 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 @@
                 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 @@
             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 @@
                 case SipSession.State.PINGING:
                     reset();
                     mReRegisterFlag = true;
-                    mState = SipSession.State.READY_TO_CALL;
                     break;
 
                 default:
@@ -877,6 +876,7 @@
         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 @@
                         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 @@
             // 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 @@
                 // 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 @@
                 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 @@
                 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 @@
 
 } // 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 @@
     {"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 @@
 // 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 @@
 //   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 @@
     void encode(int tick, AudioStream *chain);
     void decode(int tick);
 
-private:
     enum {
         NORMAL = 0,
         SEND_ONLY = 1,
@@ -93,6 +96,7 @@
         LAST_MODE = 2,
     };
 
+private:
     int mMode;
     int mSocket;
     sockaddr_storage mRemote;
@@ -111,6 +115,7 @@
     int mBufferMask;
     int mBufferHead;
     int mBufferTail;
+    int mLatencyTimer;
     int mLatencyScore;
 
     uint16_t mSequence;
@@ -159,12 +164,13 @@
     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 @@
         }
     }
 
-    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 @@
         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 @@
     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 @@
     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 @@
     }
 
     // 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 @@
         // 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 @@
         }
     }
     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 @@
     bool add(AudioStream *stream);
     bool remove(int socket);
 
-private:
     enum {
         ON_HOLD = 0,
         MUTED = 1,
@@ -459,6 +470,7 @@
         LAST_MODE = 3,
     };
 
+private:
     AudioStream *mChain;
     int mEventQueue;
     volatile int mDtmfEvent;
@@ -680,7 +692,7 @@
     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 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 33b88e43..f5efc17 100644
--- a/voip/jni/rtp/RtpStream.cpp
+++ b/voip/jni/rtp/RtpStream.cpp
@@ -88,13 +88,11 @@
 
 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 @@
     int socket = env->GetIntField(thiz, gNative);
     ::close(socket);
     env->SetIntField(thiz, gNative, -1);
-    LOGD("close %d", socket);
 }
 
 JNINativeMethod gMethods[] = {