Merge "Add new rezzing-in effect of Carousel to Recents."
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/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/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 1bc0612..6799ffd 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -88,6 +88,7 @@
     private boolean mOutsideTouchable = false;
     private boolean mClippingEnabled = true;
     private boolean mSplitTouchEnabled;
+    private boolean mLayoutInScreen;
 
     private OnTouchListener mTouchInterceptor;
     
@@ -607,6 +608,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
@@ -910,7 +934,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;
         }
@@ -934,6 +959,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 f4d193f..f5e806f 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3798,6 +3798,8 @@
         if (mError != null) {
             hideError();
         }
+
+        hideControllers();
     }
 
     @Override
@@ -4165,6 +4167,15 @@
         */
 
         canvas.restore();
+
+        if (mInsertionPointCursorController != null &&
+                mInsertionPointCursorController.isShowing()) {
+            mInsertionPointCursorController.updatePosition();
+        }
+        if (mSelectionModifierCursorController != null &&
+                mSelectionModifierCursorController.isShowing()) {
+            mSelectionModifierCursorController.updatePosition();
+        }
     }
 
     @Override
@@ -4788,6 +4799,7 @@
         if (mInputMethodState != null) {
             mInputMethodState.mExtracting = req;
         }
+        hideControllers();
     }
     
     /**
@@ -6326,7 +6338,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();
+        }
     }
     
     /**
@@ -6668,11 +6684,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
@@ -6742,8 +6767,6 @@
 
                 if (hasSelection()) {
                     startSelectionActionMode();
-                } else if (mInsertionPointCursorController != null) {
-                    mInsertionPointCursorController.show();
                 }
             }
         }
@@ -7732,6 +7755,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);
@@ -7740,6 +7765,8 @@
             mContainer = new PopupWindow(TextView.this.mContext, null,
                     com.android.internal.R.attr.textSelectHandleWindowStyle);
             mContainer.setSplitTouchEnabled(true);
+            mContainer.setClippingEnabled(false);
+            mContainer.setLayoutInScreenEnabled(true);
         }
 
         @Override
@@ -7777,19 +7804,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;
         }
 
@@ -7828,6 +7854,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;
 
@@ -7836,8 +7864,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;
 
@@ -8146,7 +8176,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/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index c4421c3..1df416d 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -388,6 +388,10 @@
     synchronized void nSamplerSet(int param, int value) {
         rsnSamplerSet(mContext, param, value);
     }
+    native void rsnSamplerSet2(int con, int param, float value);
+    synchronized void nSamplerSet2(int param, float value) {
+        rsnSamplerSet2(mContext, param, value);
+    }
     native int  rsnSamplerCreate(int con);
     synchronized int nSamplerCreate() {
         return rsnSamplerCreate(mContext);
diff --git a/graphics/java/android/renderscript/Sampler.java b/graphics/java/android/renderscript/Sampler.java
index 343fcdb..b627207 100644
--- a/graphics/java/android/renderscript/Sampler.java
+++ b/graphics/java/android/renderscript/Sampler.java
@@ -130,6 +130,7 @@
         Value mWrapS;
         Value mWrapT;
         Value mWrapR;
+        float mAniso;
 
         public Builder(RenderScript rs) {
             mRS = rs;
@@ -138,6 +139,7 @@
             mWrapS = Value.WRAP;
             mWrapT = Value.WRAP;
             mWrapR = Value.WRAP;
+            mAniso = 1.0f;
         }
 
         public void setMin(Value v) {
@@ -182,6 +184,14 @@
             }
         }
 
+        public void setAnisotropy(float v) {
+            if(v >= 0.0f) {
+                mAniso = v;
+            } else {
+                throw new IllegalArgumentException("Invalid value");
+            }
+        }
+
         static synchronized Sampler internalCreate(RenderScript rs, Builder b) {
             rs.nSamplerBegin();
             rs.nSamplerSet(0, b.mMin.mID);
@@ -189,6 +199,7 @@
             rs.nSamplerSet(2, b.mWrapS.mID);
             rs.nSamplerSet(3, b.mWrapT.mID);
             rs.nSamplerSet(4, b.mWrapR.mID);
+            rs.nSamplerSet2(5, b.mAniso);
             int id = rs.nSamplerCreate();
             return new Sampler(id, rs);
         }
diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp
index 6aed11b..67a2b63 100644
--- a/graphics/jni/android_renderscript_RenderScript.cpp
+++ b/graphics/jni/android_renderscript_RenderScript.cpp
@@ -1096,6 +1096,13 @@
     rsSamplerSet(con, (RsSamplerParam)p, (RsSamplerValue)v);
 }
 
+static void
+nSamplerSet2(JNIEnv *_env, jobject _this, RsContext con, jint p, jfloat v)
+{
+    LOG_API("nSamplerSet2, con(%p), param(%i), value(%f)", con, p, v);
+    rsSamplerSet2(con, (RsSamplerParam)p, v);
+}
+
 static jint
 nSamplerCreate(JNIEnv *_env, jobject _this, RsContext con)
 {
@@ -1303,6 +1310,7 @@
 
 {"rsnSamplerBegin",                  "(I)V",                                  (void*)nSamplerBegin },
 {"rsnSamplerSet",                    "(III)V",                                (void*)nSamplerSet },
+{"rsnSamplerSet2",                   "(IIF)V",                                (void*)nSamplerSet2 },
 {"rsnSamplerCreate",                 "(I)I",                                  (void*)nSamplerCreate },
 
 {"rsnMeshCreate",                    "(III)I",                                (void*)nMeshCreate },
diff --git a/libs/rs/RenderScript.h b/libs/rs/RenderScript.h
index 7902b9a..13ae1fb 100644
--- a/libs/rs/RenderScript.h
+++ b/libs/rs/RenderScript.h
@@ -122,7 +122,8 @@
     RS_SAMPLER_MAG_FILTER,
     RS_SAMPLER_WRAP_S,
     RS_SAMPLER_WRAP_T,
-    RS_SAMPLER_WRAP_R
+    RS_SAMPLER_WRAP_R,
+    RS_SAMPLER_ANISO
 };
 
 enum RsSamplerValue {
diff --git a/libs/rs/java/Samples/res/drawable/checker.png b/libs/rs/java/Samples/res/drawable/checker.png
new file mode 100644
index 0000000..b631e1e
--- /dev/null
+++ b/libs/rs/java/Samples/res/drawable/checker.png
Binary files differ
diff --git a/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java b/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
index d4e83d3..a15c4a1 100644
--- a/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
+++ b/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
@@ -19,11 +19,12 @@
 import java.io.Writer;
 
 import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.renderscript.*;
 import android.renderscript.ProgramStore.DepthFunc;
+import android.renderscript.Sampler.Value;
 import android.util.Log;
-import android.graphics.BitmapFactory;
-import android.graphics.Bitmap;
 
 
 public class RsRenderStatesRS {
@@ -42,7 +43,7 @@
         mOptionsARGB.inScaled = false;
         mOptionsARGB.inPreferredConfig = Bitmap.Config.ARGB_8888;
         mMode = 0;
-        mMaxModes = 8;
+        mMaxModes = 9;
         initRS();
     }
 
@@ -53,6 +54,8 @@
     private Sampler mLinearWrap;
     private Sampler mMipLinearWrap;
     private Sampler mNearestClamp;
+    private Sampler mMipLinearAniso8;
+    private Sampler mMipLinearAniso15;
 
     private ProgramStore mProgStoreBlendNoneDepth;
     private ProgramStore mProgStoreBlendNone;
@@ -74,10 +77,12 @@
 
     private ProgramRaster mCullBack;
     private ProgramRaster mCullFront;
+    private ProgramRaster mCullNone;
 
     private Allocation mTexTorus;
     private Allocation mTexOpaque;
     private Allocation mTexTransparent;
+    private Allocation mTexChecker;
 
     private Allocation mAllocPV;
 
@@ -243,10 +248,12 @@
         mTexTorus = loadTextureRGB(R.drawable.torusmap);
         mTexOpaque = loadTextureRGB(R.drawable.data);
         mTexTransparent = loadTextureARGB(R.drawable.leaf);
+        mTexChecker = loadTextureRGB(R.drawable.checker);
 
         mScript.set_gTexTorus(mTexTorus);
         mScript.set_gTexOpaque(mTexOpaque);
         mScript.set_gTexTransparent(mTexTransparent);
+        mScript.set_gTexChecker(mTexChecker);
     }
 
     private void initFonts() {
@@ -295,18 +302,32 @@
         mNearestClamp = Sampler.CLAMP_NEAREST(mRS);
         mMipLinearWrap = Sampler.WRAP_LINEAR_MIP_LINEAR(mRS);
 
+        bs = new Sampler.Builder(mRS);
+        bs.setMin(Sampler.Value.LINEAR_MIP_LINEAR);
+        bs.setMag(Sampler.Value.LINEAR);
+        bs.setWrapS(Sampler.Value.WRAP);
+        bs.setWrapT(Sampler.Value.WRAP);
+        bs.setAnisotropy(8.0f);
+        mMipLinearAniso8 = bs.create();
+        bs.setAnisotropy(15.0f);
+        mMipLinearAniso15 = bs.create();
+
         mScript.set_gLinearClamp(mLinearClamp);
         mScript.set_gLinearWrap(mLinearWrap);
         mScript.set_gMipLinearWrap(mMipLinearWrap);
+        mScript.set_gMipLinearAniso8(mMipLinearAniso8);
+        mScript.set_gMipLinearAniso15(mMipLinearAniso15);
         mScript.set_gNearestClamp(mNearestClamp);
     }
 
     private void initProgramRaster() {
         mCullBack = ProgramRaster.CULL_BACK(mRS);
         mCullFront = ProgramRaster.CULL_FRONT(mRS);
+        mCullNone = ProgramRaster.CULL_NONE(mRS);
 
         mScript.set_gCullBack(mCullBack);
         mScript.set_gCullFront(mCullFront);
+        mScript.set_gCullNone(mCullNone);
     }
 
     private void initRS() {
diff --git a/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs b/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
index 659e1e4..b471504 100644
--- a/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
+++ b/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
@@ -31,6 +31,7 @@
 rs_allocation gTexOpaque;
 rs_allocation gTexTorus;
 rs_allocation gTexTransparent;
+rs_allocation gTexChecker;
 
 rs_mesh gMbyNMesh;
 rs_mesh gTorusMesh;
@@ -47,10 +48,13 @@
 rs_sampler gLinearClamp;
 rs_sampler gLinearWrap;
 rs_sampler gMipLinearWrap;
+rs_sampler gMipLinearAniso8;
+rs_sampler gMipLinearAniso15;
 rs_sampler gNearestClamp;
 
 rs_program_raster gCullBack;
 rs_program_raster gCullFront;
+rs_program_raster gCullNone;
 
 // Custom vertex shader compunents
 VertexShaderConstants *gVSConstants;
@@ -64,11 +68,11 @@
 
 #pragma rs export_var(gProgVertex, gProgFragmentColor, gProgFragmentTexture)
 #pragma rs export_var(gProgStoreBlendNoneDepth, gProgStoreBlendNone, gProgStoreBlendAlpha, gProgStoreBlendAdd)
-#pragma rs export_var(gTexOpaque, gTexTorus, gTexTransparent)
+#pragma rs export_var(gTexOpaque, gTexTorus, gTexTransparent, gTexChecker)
 #pragma rs export_var(gMbyNMesh, gTorusMesh)
 #pragma rs export_var(gFontSans, gFontSerif, gFontSerifBold, gFontSerifItalic, gFontSerifBoldItalic, gFontMono)
-#pragma rs export_var(gLinearClamp, gLinearWrap, gMipLinearWrap, gNearestClamp)
-#pragma rs export_var(gCullBack, gCullFront)
+#pragma rs export_var(gLinearClamp, gLinearWrap, gMipLinearWrap, gMipLinearAniso8, gMipLinearAniso15, gNearestClamp)
+#pragma rs export_var(gCullBack, gCullFront, gCullNone)
 #pragma rs export_var(gVSConstants, gFSConstants, gVSInputs, gProgVertexCustom, gProgFragmentCustom, gProgFragmentMultitex)
 
 //What we are showing
@@ -110,7 +114,7 @@
     rsgBindProgramVertex(gProgVertex);
     // Setup the projectioni matrix
     rs_matrix4x4 proj;
-    rsMatrixLoadOrtho(&proj, 0, rsgGetWidth(), rsgGetHeight(), 0, -1,1);
+    rsMatrixLoadOrtho(&proj, 0, rsgGetWidth(), rsgGetHeight(), 0, -500, 500);
     rsgProgramVertexLoadProjectionMatrix(&proj);
 }
 
@@ -276,14 +280,12 @@
                          startX + width, startY + height, 0, 1.5, 1.5,
                          startX + width, startY, 0, 1.5, 0);
 
-
     rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
     rsgBindFont(gFontMono);
     rsgDrawText("Filtering: linear clamp", 10, 290);
     rsgDrawText("Filtering: linear wrap", 10, 590);
     rsgDrawText("Filtering: nearest clamp", 310, 290);
     rsgDrawText("Filtering: miplinear wrap", 310, 590);
-
 }
 
 float gTorusRotation = 0;
@@ -430,7 +432,7 @@
     rsgBindSampler(gProgFragmentMultitex, 0, gLinearClamp);
     rsgBindSampler(gProgFragmentMultitex, 1, gLinearWrap);
     rsgBindSampler(gProgFragmentMultitex, 2, gLinearClamp);
-    rsgBindTexture(gProgFragmentMultitex, 0, gTexOpaque);
+    rsgBindTexture(gProgFragmentMultitex, 0, gTexChecker);
     rsgBindTexture(gProgFragmentMultitex, 1, gTexTorus);
     rsgBindTexture(gProgFragmentMultitex, 2, gTexTransparent);
 
@@ -446,6 +448,69 @@
     rsgDrawText("Custom shader with multitexturing", 10, 280);
 }
 
+float gAnisoTime = 0.0f;
+uint anisoMode = 0;
+void displayAnisoSample() {
+
+    gAnisoTime += gDt;
+
+    rsgBindProgramVertex(gProgVertex);
+    float aspect = (float)rsgGetWidth() / (float)rsgGetHeight();
+    rs_matrix4x4 proj;
+    rsMatrixLoadPerspective(&proj, 30.0f, aspect, 0.1f, 100.0f);
+    rsgProgramVertexLoadProjectionMatrix(&proj);
+
+    rs_matrix4x4 matrix;
+    // Fragment shader with texture
+    rsgBindProgramStore(gProgStoreBlendNone);
+    rsgBindProgramFragment(gProgFragmentTexture);
+    rsMatrixLoadTranslate(&matrix, 0.0f, 0.0f, -10.0f);
+    rsMatrixRotate(&matrix, -80, 1.0f, 0.0f, 0.0f);
+    rsgProgramVertexLoadModelMatrix(&matrix);
+
+    rsgBindProgramRaster(gCullNone);
+
+    rsgBindTexture(gProgFragmentTexture, 0, gTexChecker);
+
+    if(gAnisoTime >= 5.0f) {
+        gAnisoTime = 0.0f;
+        anisoMode ++;
+        anisoMode = anisoMode % 3;
+    }
+
+    if(anisoMode == 0) {
+        rsgBindSampler(gProgFragmentTexture, 0, gMipLinearAniso8);
+    }
+    else if(anisoMode == 1) {
+        rsgBindSampler(gProgFragmentTexture, 0, gMipLinearAniso15);
+    }
+    else {
+        rsgBindSampler(gProgFragmentTexture, 0, gMipLinearWrap);
+    }
+
+    float startX = -15;
+    float startY = -15;
+    float width = 30;
+    float height = 30;
+    rsgDrawQuadTexCoords(startX, startY, 0, 0, 0,
+                         startX, startY + height, 0, 0, 10,
+                         startX + width, startY + height, 0, 10, 10,
+                         startX + width, startY, 0, 10, 0);
+
+    rsgBindProgramRaster(gCullBack);
+
+    rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
+    rsgBindFont(gFontMono);
+    if(anisoMode == 0) {
+        rsgDrawText("Anisotropic filtering 8", 10, 40);
+    }
+    else if(anisoMode == 1) {
+        rsgDrawText("Anisotropic filtering 15", 10, 40);
+    }
+    else {
+        rsgDrawText("Miplinear filtering", 10, 40);
+    }
+}
 
 int root(int launchID) {
 
@@ -479,6 +544,9 @@
     case 7:
         displayMultitextureSample();
         break;
+    case 8:
+        displayAnisoSample();
+        break;
     }
 
     return 10;
diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec
index 2b7928f..a4752f4 100644
--- a/libs/rs/rs.spec
+++ b/libs/rs/rs.spec
@@ -288,6 +288,11 @@
 	param RsSamplerValue value
 	}
 
+SamplerSet2 {
+	param RsSamplerParam p
+	param float value
+	}
+
 SamplerCreate {
 	ret RsSampler
 	}
diff --git a/libs/rs/rsAdapter.cpp b/libs/rs/rsAdapter.cpp
index b4ec250..ef69b75 100644
--- a/libs/rs/rsAdapter.cpp
+++ b/libs/rs/rsAdapter.cpp
@@ -183,7 +183,6 @@
 
     uint32_t eSize = mAllocation.get()->getType()->getElementSizeBytes();
     uint32_t lineSize = eSize * w;
-    uint32_t destW = getDimX();
 
     const uint8_t *src = static_cast<const uint8_t *>(data);
     for (uint32_t line=yoff; line < (yoff+h); line++) {
diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp
index 87c4f2b..0356e4d 100644
--- a/libs/rs/rsAllocation.cpp
+++ b/libs/rs/rsAllocation.cpp
@@ -293,7 +293,6 @@
     }
 
     for (uint32_t line=yoff; line < (yoff+h); line++) {
-        uint8_t * ptr = static_cast<uint8_t *>(mPtr);
         if (mType->getElement()->getHasReferences()) {
             incRefs(src, w);
             decRefs(dst, w);
diff --git a/libs/rs/rsContextHostStub.h b/libs/rs/rsContextHostStub.h
index f30915e..06298e8 100644
--- a/libs/rs/rsContextHostStub.h
+++ b/libs/rs/rsContextHostStub.h
@@ -111,6 +111,9 @@
         bool mLogScripts;
         bool mLogObjects;
         bool mLogShaders;
+        bool mLogShadersAttr;
+        bool mLogShadersUniforms;
+        bool mLogVisual;
     } props;
 
     void dumpDebug() const {    }
@@ -120,6 +123,7 @@
     mutable const ObjectBase * mObjHead;
 
     bool ext_OES_texture_npot() const {return mGL.OES_texture_npot;}
+    float ext_texture_max_aniso() const {return 1.0f;}
     uint32_t getMaxFragmentTextures() const {return mGL.mMaxFragmentTextureImageUnits;}
     uint32_t getMaxFragmentUniformVectors() const {return mGL.mMaxFragmentUniformVectors;}
     uint32_t getMaxVertexUniformVectors() const {return mGL.mMaxVertexUniformVectors;}
diff --git a/libs/rs/rsSampler.cpp b/libs/rs/rsSampler.cpp
index c6a848c..180d78e 100644
--- a/libs/rs/rsSampler.cpp
+++ b/libs/rs/rsSampler.cpp
@@ -44,7 +44,8 @@
                  RsSamplerValue minFilter,
                  RsSamplerValue wrapS,
                  RsSamplerValue wrapT,
-                 RsSamplerValue wrapR) : ObjectBase(rsc)
+                 RsSamplerValue wrapR,
+                 float aniso) : ObjectBase(rsc)
 {
     mAllocFile = __FILE__;
     mAllocLine = __LINE__;
@@ -53,6 +54,7 @@
     mWrapS = wrapS;
     mWrapT = wrapT;
     mWrapR = wrapR;
+    mAniso = aniso;
 }
 
 Sampler::~Sampler()
@@ -93,6 +95,11 @@
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, trans[mWrapT]);
     }
 
+    float anisoValue = rsMin(rsc->ext_texture_max_aniso(), mAniso);
+    if(rsc->ext_texture_max_aniso() > 1.0f) {
+        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisoValue);
+    }
+
     rsc->checkError("Sampler::setupGL2 tex env");
 }
 
@@ -147,6 +154,7 @@
     ss->mWrapS = RS_SAMPLER_WRAP;
     ss->mWrapT = RS_SAMPLER_WRAP;
     ss->mWrapR = RS_SAMPLER_WRAP;
+    ss->mAniso = 1.0f;
 }
 
 void rsi_SamplerSet(Context *rsc, RsSamplerParam param, RsSamplerValue value)
@@ -169,21 +177,37 @@
     case RS_SAMPLER_WRAP_R:
         ss->mWrapR = value;
         break;
+    default:
+        LOGE("Attempting to set invalid value on sampler");
+        break;
     }
+}
 
+void rsi_SamplerSet2(Context *rsc, RsSamplerParam param, float value)
+{
+    SamplerState * ss = &rsc->mStateSampler;
+
+    switch(param) {
+    case RS_SAMPLER_ANISO:
+        ss->mAniso = value;
+        break;
+    default:
+        LOGE("Attempting to set invalid value on sampler");
+        break;
+    }
 }
 
 RsSampler rsi_SamplerCreate(Context *rsc)
 {
     SamplerState * ss = &rsc->mStateSampler;
 
-
     Sampler * s = new Sampler(rsc,
                               ss->mMagFilter,
                               ss->mMinFilter,
                               ss->mWrapS,
                               ss->mWrapT,
-                              ss->mWrapR);
+                              ss->mWrapR,
+                              ss->mAniso);
     s->incUserRef();
     return s;
 }
diff --git a/libs/rs/rsSampler.h b/libs/rs/rsSampler.h
index 32a8efd..4946355 100644
--- a/libs/rs/rsSampler.h
+++ b/libs/rs/rsSampler.h
@@ -36,7 +36,8 @@
             RsSamplerValue minFilter,
             RsSamplerValue wrapS,
             RsSamplerValue wrapT,
-            RsSamplerValue wrapR);
+            RsSamplerValue wrapR,
+            float aniso = 1.0f);
 
     virtual ~Sampler();
 
@@ -56,6 +57,7 @@
     RsSamplerValue mWrapS;
     RsSamplerValue mWrapT;
     RsSamplerValue mWrapR;
+    float mAniso;
 
     int32_t mBoundSlot;
 
@@ -74,6 +76,7 @@
     RsSamplerValue mWrapS;
     RsSamplerValue mWrapT;
     RsSamplerValue mWrapR;
+    float mAniso;
 
 
     ObjectBaseRef<Sampler> mSamplers[RS_MAX_SAMPLER_SLOT];
diff --git a/libs/rs/rsType.cpp b/libs/rs/rsType.cpp
index 1ee07cd..fc037a3 100644
--- a/libs/rs/rsType.cpp
+++ b/libs/rs/rsType.cpp
@@ -321,8 +321,6 @@
         break;
     }
 
-
-    int32_t arrayNum = dim - RS_DIMENSION_ARRAY_0;
     if ((dim < 0) || (dim > RS_DIMENSION_MAX)) {
         LOGE("rsTypeAdd: Bad dimension");
         //error
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index cfad939..2c13069 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -13,6 +13,6 @@
 LOCAL_PACKAGE_NAME := SystemUI
 LOCAL_CERTIFICATE := platform
 
-LOCAL_PROGUARD_FLAGS := -include $(LOCAL_PATH)/proguard.flags
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
 include $(BUILD_PACKAGE)
diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java
index 52f5716..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,16 +512,18 @@
 
     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 == null)
-                        ? "no session"
+                        ? 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 130fe9f..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);
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[] = {