Merge "Better error codes for missing files"
diff --git a/api/current.txt b/api/current.txt
index 876d555..8595eb3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11116,6 +11116,7 @@
 
   public class ConnectivityManager {
     method public android.net.NetworkInfo getActiveNetworkInfo();
+    method public android.net.NetworkQuotaInfo getActiveNetworkQuotaInfo();
     method public android.net.NetworkInfo[] getAllNetworkInfo();
     method public boolean getBackgroundDataSetting();
     method public android.net.NetworkInfo getNetworkInfo(int);
@@ -11130,7 +11131,7 @@
     field public static final int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1
     field public static final java.lang.String EXTRA_EXTRA_INFO = "extraInfo";
     field public static final java.lang.String EXTRA_IS_FAILOVER = "isFailover";
-    field public static final java.lang.String EXTRA_NETWORK_INFO = "networkInfo";
+    field public static final deprecated java.lang.String EXTRA_NETWORK_INFO = "networkInfo";
     field public static final java.lang.String EXTRA_NO_CONNECTIVITY = "noConnectivity";
     field public static final java.lang.String EXTRA_OTHER_NETWORK_INFO = "otherNetwork";
     field public static final java.lang.String EXTRA_REASON = "reason";
@@ -11275,6 +11276,16 @@
     enum_constant public static final android.net.NetworkInfo.State UNKNOWN;
   }
 
+  public class NetworkQuotaInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public long getEstimatedBytes();
+    method public long getHardLimitBytes();
+    method public long getSoftLimitBytes();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+    field public static final long NO_LIMIT = -1L; // 0xffffffffffffffffL
+  }
+
   public class ParseException extends java.lang.RuntimeException {
     field public java.lang.String response;
   }
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index a564d97..eb9cd21 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -16,10 +16,11 @@
 
 package android.net;
 
+import static com.android.internal.util.Preconditions.checkNotNull;
+
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.os.Binder;
-import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 
 import java.net.InetAddress;
@@ -67,11 +68,19 @@
      * is set to {@code true} if there are no connected networks at all.
      */
     public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
+
     /**
      * The lookup key for a {@link NetworkInfo} object. Retrieve with
      * {@link android.content.Intent#getParcelableExtra(String)}.
+     *
+     * @deprecated Since {@link NetworkInfo} can vary based on UID, applications
+     *             should always obtain network information through
+     *             {@link #getActiveNetworkInfo()} or
+     *             {@link #getAllNetworkInfo()}.
      */
+    @Deprecated
     public static final String EXTRA_NETWORK_INFO = "networkInfo";
+
     /**
      * The lookup key for a boolean that indicates whether a connect event
      * is for a network to which the connectivity manager was failing over
@@ -515,6 +524,19 @@
     }
 
     /**
+     * Return quota status for the current active network, or {@code null} if no
+     * network is active. Quota status can change rapidly, so these values
+     * shouldn't be cached.
+     */
+    public NetworkQuotaInfo getActiveNetworkQuotaInfo() {
+        try {
+            return mService.getActiveNetworkQuotaInfo();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
      * Gets the value of the setting for enabling Mobile data.
      *
      * @return Whether mobile data is enabled.
@@ -546,10 +568,7 @@
      * {@hide}
      */
     public ConnectivityManager(IConnectivityManager service) {
-        if (service == null) {
-            throw new IllegalArgumentException("missing IConnectivityManager");
-        }
-        mService = service;
+        mService = checkNotNull(service, "missing IConnectivityManager");
     }
 
     /**
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index b1d99a4..f391200 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -18,6 +18,7 @@
 
 import android.net.LinkProperties;
 import android.net.NetworkInfo;
+import android.net.NetworkQuotaInfo;
 import android.net.NetworkState;
 import android.net.ProxyProperties;
 import android.os.IBinder;
@@ -47,6 +48,8 @@
 
     NetworkState[] getAllNetworkState();
 
+    NetworkQuotaInfo getActiveNetworkQuotaInfo();
+
     boolean setRadios(boolean onOff);
 
     boolean setRadio(int networkType, boolean turnOn);
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 3e07b0a..633c38e0 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -18,6 +18,8 @@
 
 import android.net.INetworkPolicyListener;
 import android.net.NetworkPolicy;
+import android.net.NetworkQuotaInfo;
+import android.net.NetworkState;
 import android.net.NetworkTemplate;
 
 /**
@@ -27,6 +29,7 @@
  */
 interface INetworkPolicyManager {
 
+    /** Control UID policies. */
     void setUidPolicy(int uid, int policy);
     int getUidPolicy(int uid);
 
@@ -35,12 +38,17 @@
     void registerListener(INetworkPolicyListener listener);
     void unregisterListener(INetworkPolicyListener listener);
 
+    /** Control network policies atomically. */
     void setNetworkPolicies(in NetworkPolicy[] policies);
     NetworkPolicy[] getNetworkPolicies();
 
+    /** Snooze limit on policy matching given template. */
     void snoozePolicy(in NetworkTemplate template);
 
+    /** Control if background data is restricted system-wide. */
     void setRestrictBackground(boolean restrictBackground);
     boolean getRestrictBackground();
 
+    NetworkQuotaInfo getNetworkQuotaInfo(in NetworkState state);
+
 }
diff --git a/core/java/android/net/NetworkQuotaInfo.aidl b/core/java/android/net/NetworkQuotaInfo.aidl
new file mode 100644
index 0000000..98a02c4
--- /dev/null
+++ b/core/java/android/net/NetworkQuotaInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+parcelable NetworkQuotaInfo;
diff --git a/core/java/android/net/NetworkQuotaInfo.java b/core/java/android/net/NetworkQuotaInfo.java
new file mode 100644
index 0000000..b85f925
--- /dev/null
+++ b/core/java/android/net/NetworkQuotaInfo.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Information about quota status on a specific network.
+ */
+public class NetworkQuotaInfo implements Parcelable {
+    private final long mEstimatedBytes;
+    private final long mSoftLimitBytes;
+    private final long mHardLimitBytes;
+
+    public static final long NO_LIMIT = -1;
+
+    /** {@hide} */
+    public NetworkQuotaInfo(long estimatedBytes, long softLimitBytes, long hardLimitBytes) {
+        mEstimatedBytes = estimatedBytes;
+        mSoftLimitBytes = softLimitBytes;
+        mHardLimitBytes = hardLimitBytes;
+    }
+
+    /** {@hide} */
+    public NetworkQuotaInfo(Parcel in) {
+        mEstimatedBytes = in.readLong();
+        mSoftLimitBytes = in.readLong();
+        mHardLimitBytes = in.readLong();
+    }
+
+    public long getEstimatedBytes() {
+        return mEstimatedBytes;
+    }
+
+    public long getSoftLimitBytes() {
+        return mSoftLimitBytes;
+    }
+
+    public long getHardLimitBytes() {
+        return mHardLimitBytes;
+    }
+
+    /** {@inheritDoc} */
+    public int describeContents() {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeLong(mEstimatedBytes);
+        out.writeLong(mSoftLimitBytes);
+        out.writeLong(mHardLimitBytes);
+    }
+
+    public static final Creator<NetworkQuotaInfo> CREATOR = new Creator<NetworkQuotaInfo>() {
+        public NetworkQuotaInfo createFromParcel(Parcel in) {
+            return new NetworkQuotaInfo(in);
+        }
+
+        public NetworkQuotaInfo[] newArray(int size) {
+            return new NetworkQuotaInfo[size];
+        }
+    };
+}
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index 4ffabb1..7a4b811 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -405,11 +405,10 @@
             final long curEnd = randomLong(r, curStart, end);
             entry.rxBytes = randomLong(r, 0, rx);
             entry.txBytes = randomLong(r, 0, tx);
-
-            recordData(curStart, curEnd, entry);
-
             rx -= entry.rxBytes;
             tx -= entry.txBytes;
+
+            recordData(curStart, curEnd, entry);
         }
     }
 
diff --git a/core/java/android/server/BluetoothPanProfileHandler.java b/core/java/android/server/BluetoothPanProfileHandler.java
index 0d63e19..ff22a45 100644
--- a/core/java/android/server/BluetoothPanProfileHandler.java
+++ b/core/java/android/server/BluetoothPanProfileHandler.java
@@ -173,7 +173,7 @@
 
                 if (!mBluetoothService.disconnectPanServerDeviceNative(objectPath,
                         device.getAddress(),
-                        panDevice.mIfaceAddr)) {
+                        panDevice.mIface)) {
                     errorLog("could not disconnect Pan Server Device "+device.getAddress());
 
                     // Restore prev state
@@ -291,6 +291,7 @@
             panDevice.mState = state;
             panDevice.mIfaceAddr = ifaceAddr;
             panDevice.mLocalRole = role;
+            panDevice.mIface = iface;
         }
 
         Intent intent = new Intent(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 66d2641..f2b6b1f 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -635,16 +635,8 @@
             destroySurface();
 
             // Create an EGL surface we can render into.
-            mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, holder, null);
-
-            if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
-                int error = sEgl.eglGetError();
-                if (error == EGL_BAD_NATIVE_WINDOW) {
-                    Log.e(LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
-                    return null;
-                }
-                throw new RuntimeException("createWindowSurface failed "
-                        + getEGLErrorString(error));
+            if (!createSurface(holder)) {
+                return null;
             }
 
             /*
@@ -713,18 +705,34 @@
             // Cancels any existing buffer to ensure we'll get a buffer
             // of the right size before we call eglSwapBuffers
             sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
-            sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
+
+            if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) {
+                sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
+                mEglSurface = null;
+                setEnabled(false);
+            }
+
+            if (holder.getSurface().isValid()) {
+                if (!createSurface(holder)) {
+                    return;
+                }
+                setEnabled(true);                
+            }
+        }
+
+        private boolean createSurface(SurfaceHolder holder) {
             mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, holder, null);
 
             if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
                 int error = sEgl.eglGetError();
                 if (error == EGL_BAD_NATIVE_WINDOW) {
                     Log.e(LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
-                    return;
+                    return false;
                 }
                 throw new RuntimeException("createWindowSurface failed "
                         + getEGLErrorString(error));
             }
+            return true;
         }
 
         @Override
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 911bf2f..ec4c5a29 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1253,6 +1253,11 @@
                         mScroller.abortAnimation();
                     }
                     disposeResizeBuffer();
+                    // Our surface is gone
+                    if (mAttachInfo.mHardwareRenderer != null &&
+                            mAttachInfo.mHardwareRenderer.isEnabled()) {
+                        mAttachInfo.mHardwareRenderer.destroy(true);
+                    }
                 } else if (surfaceGenerationId != mSurface.getGenerationId() &&
                         mSurfaceHolder == null && mAttachInfo.mHardwareRenderer != null) {
                     fullRedrawNeeded = true;
@@ -1273,7 +1278,7 @@
                 }
             } catch (RemoteException e) {
             }
-            
+
             if (DEBUG_ORIENTATION) Log.v(
                     TAG, "Relayout returned: frame=" + frame + ", surface=" + mSurface);
 
diff --git a/core/java/android/webkit/DeviceMotionService.java b/core/java/android/webkit/DeviceMotionService.java
index 7d7a0f0..b4d5759 100755
--- a/core/java/android/webkit/DeviceMotionService.java
+++ b/core/java/android/webkit/DeviceMotionService.java
@@ -99,6 +99,7 @@
         mUpdateRunnable = new Runnable() {
             @Override
             public void run() {
+                assert mIsRunning;
                 mManager.onMotionChange(new Double(mLastAcceleration[0]),
                         new Double(mLastAcceleration[1]), new Double(mLastAcceleration[2]),
                         INTERVAL_MILLIS);
@@ -157,6 +158,11 @@
         assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
         assert(event.sensor.getType() == Sensor.TYPE_ACCELEROMETER);
 
+        // We may get callbacks after the call to getSensorManager().unregisterListener() returns.
+        if (!mIsRunning) {
+            return;
+        }
+
         boolean firstData = mLastAcceleration == null;
         mLastAcceleration = event.values;
         if (firstData) {
diff --git a/core/java/android/webkit/DeviceOrientationService.java b/core/java/android/webkit/DeviceOrientationService.java
index f3c0576..47c8ab7 100755
--- a/core/java/android/webkit/DeviceOrientationService.java
+++ b/core/java/android/webkit/DeviceOrientationService.java
@@ -188,6 +188,7 @@
         assert(event.values.length == 3);
         assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
 
+        // We may get callbacks after the call to getSensorManager().unregisterListener() returns.
         if (!mIsRunning) {
             return;
         }
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index f354c6e..d977029 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -250,6 +250,7 @@
      * {@inheritDoc}
      */
     public GridLayout(Context context) {
+        //noinspection NullableProblems
         this(context, null);
     }
 
@@ -519,14 +520,6 @@
         return result;
     }
 
-    private static int sum(int[] a) {
-        int result = 0;
-        for (int i = 0, N = a.length; i < N; i++) {
-            result += a[i];
-        }
-        return result;
-    }
-
     @SuppressWarnings("unchecked")
     private static <T> T[] append(T[] a, T[] b) {
         T[] result = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length + b.length);
@@ -553,6 +546,7 @@
         }
     }
 
+    /** @noinspection UnusedParameters*/
     private int getDefaultMargin(View c, boolean horizontal, boolean leading) {
         if (c.getClass() == Space.class) {
             return 0;
@@ -576,7 +570,7 @@
         return getDefaultMargin(c, isAtEdge, horizontal, leading);
     }
 
-    private int getMargin(View view, boolean horizontal, boolean leading) {
+    private int getMargin1(View view, boolean horizontal, boolean leading) {
         LayoutParams lp = getLayoutParams(view);
         int margin = horizontal ?
                 (leading ? lp.leftMargin : lp.rightMargin) :
@@ -584,6 +578,19 @@
         return margin == UNDEFINED ? getDefaultMarginValue(view, lp, horizontal, leading) : margin;
     }
 
+    private int getMargin(View view, boolean horizontal, boolean leading) {
+        if (mAlignmentMode == ALIGN_MARGINS) {
+            return getMargin1(view, horizontal, leading);
+        } else {
+            Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
+            int[] margins = leading ? axis.getLeadingMargins() : axis.getTrailingMargins();
+            LayoutParams lp = getLayoutParams(view);
+            Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
+            int index = leading ? spec.span.min : spec.span.max;
+            return margins[index];
+        }
+    }
+
     private int getTotalMargin(View child, boolean horizontal) {
         return getMargin(child, horizontal, true) + getMargin(child, horizontal, false);
     }
@@ -733,15 +740,6 @@
         graphics.drawLine(dx + x1, dy + y1, dx + x2, dy + y2, paint);
     }
 
-    private void drawRectangle(Canvas graphics, int x1, int y1, int x2, int y2, Paint paint) {
-        x2 = x2 - 1;
-        y2 = y2 - 1;
-        graphics.drawLine(x1, y1, x1, y2, paint);
-        graphics.drawLine(x1, y1, x2, y1, paint);
-        graphics.drawLine(x1, y2, x2, y2, paint);
-        graphics.drawLine(x2, y1, x2, y2, paint);
-    }
-
     @Override
     protected void onDraw(Canvas canvas) {
         super.onDraw(canvas);
@@ -751,6 +749,7 @@
             int width = getWidth() - getPaddingLeft() - getPaddingRight();
 
             Paint paint = new Paint();
+            paint.setStyle(Paint.Style.STROKE);
             paint.setColor(Color.argb(50, 255, 255, 255));
 
             int[] xs = mHorizontalAxis.locations;
@@ -773,22 +772,18 @@
             paint.setColor(Color.BLUE);
             for (int i = 0; i < getChildCount(); i++) {
                 View c = getChildAt(i);
-                drawRectangle(canvas,
-                        c.getLeft(),
-                        c.getTop(),
-                        c.getRight(),
-                        c.getBottom(), paint);
+                canvas.drawRect(c.getLeft(), c.getTop(), c.getRight(), c.getBottom(), paint);
             }
 
             // Draw margins
             paint.setColor(Color.MAGENTA);
             for (int i = 0; i < getChildCount(); i++) {
                 View c = getChildAt(i);
-                drawRectangle(canvas,
-                        c.getLeft() - getMargin(c, true, true),
-                        c.getTop() - getMargin(c, false, true),
-                        c.getRight() + getMargin(c, true, false),
-                        c.getBottom() + getMargin(c, false, false), paint);
+                canvas.drawRect(
+                        c.getLeft() - getMargin1(c, true, true),
+                        c.getTop() - getMargin1(c, false, true),
+                        c.getRight() + getMargin1(c, true, false),
+                        c.getBottom() + getMargin1(c, false, false), paint);
             }
         }
     }
@@ -875,11 +870,7 @@
         if (isGone(c)) {
             return 0;
         }
-        int result = getMeasurement(c, horizontal);
-        if (mAlignmentMode == ALIGN_MARGINS) {
-            return result + getTotalMargin(c, horizontal);
-        }
-        return result;
+        return getMeasurement(c, horizontal) + getTotalMargin(c, horizontal);
     }
 
     @Override
@@ -920,6 +911,9 @@
         mHorizontalAxis.layout(targetWidth - paddingLeft - paddingRight);
         mVerticalAxis.layout(targetHeight - paddingTop - paddingBottom);
 
+        int[] hLocations = mHorizontalAxis.getLocations();
+        int[] vLocations = mVerticalAxis.getLocations();
+
         for (int i = 0, N = getChildCount(); i < N; i++) {
             View c = getChildAt(i);
             if (isGone(c)) continue;
@@ -930,11 +924,11 @@
             Interval colSpan = columnSpec.span;
             Interval rowSpan = rowSpec.span;
 
-            int x1 = mHorizontalAxis.getLocationIncludingMargin(true, colSpan.min);
-            int y1 = mVerticalAxis.getLocationIncludingMargin(true, rowSpan.min);
+            int x1 = hLocations[colSpan.min];
+            int y1 = vLocations[rowSpan.min];
 
-            int x2 = mHorizontalAxis.getLocationIncludingMargin(false, colSpan.max);
-            int y2 = mVerticalAxis.getLocationIncludingMargin(false, rowSpan.max);
+            int x2 = hLocations[colSpan.max];
+            int y2 = vLocations[rowSpan.max];
 
             int cellWidth = x2 - x1;
             int cellHeight = y2 - y1;
@@ -951,36 +945,29 @@
             Bounds rowBounds = mVerticalAxis.getGroupBounds().getValue(i);
 
             // Gravity offsets: the location of the alignment group relative to its cell group.
+            //noinspection NullableProblems
             int c2ax = protect(hAlign.getAlignmentValue(null, cellWidth - colBounds.size(true)));
+            //noinspection NullableProblems
             int c2ay = protect(vAlign.getAlignmentValue(null, cellHeight - rowBounds.size(true)));
 
-            if (mAlignmentMode == ALIGN_MARGINS) {
-                int leftMargin = getMargin(c, true, true);
-                int topMargin = getMargin(c, false, true);
-                int rightMargin = getMargin(c, true, false);
-                int bottomMargin = getMargin(c, false, false);
+            int leftMargin = getMargin(c, true, true);
+            int topMargin = getMargin(c, false, true);
+            int rightMargin = getMargin(c, true, false);
+            int bottomMargin = getMargin(c, false, false);
 
-                // Same calculation as getMeasurementIncludingMargin()
-                int mWidth = leftMargin + pWidth + rightMargin;
-                int mHeight = topMargin + pHeight + bottomMargin;
+            // Same calculation as getMeasurementIncludingMargin()
+            int mWidth = leftMargin + pWidth + rightMargin;
+            int mHeight = topMargin + pHeight + bottomMargin;
 
-                // Alignment offsets: the location of the view relative to its alignment group.
-                int a2vx = colBounds.getOffset(c, hAlign, mWidth);
-                int a2vy = rowBounds.getOffset(c, vAlign, mHeight);
+            // Alignment offsets: the location of the view relative to its alignment group.
+            int a2vx = colBounds.getOffset(c, hAlign, mWidth);
+            int a2vy = rowBounds.getOffset(c, vAlign, mHeight);
 
-                dx = c2ax + a2vx + leftMargin;
-                dy = c2ay + a2vy + topMargin;
+            dx = c2ax + a2vx + leftMargin;
+            dy = c2ay + a2vy + topMargin;
 
-                cellWidth -= leftMargin + rightMargin;
-                cellHeight -= topMargin + bottomMargin;
-            } else {
-                // Alignment offsets: the location of the view relative to its alignment group.
-                int a2vx = colBounds.getOffset(c, hAlign, pWidth);
-                int a2vy = rowBounds.getOffset(c, vAlign, pHeight);
-
-                dx = c2ax + a2vx;
-                dy = c2ay + a2vy;
-            }
+            cellWidth -= leftMargin + rightMargin;
+            cellHeight -= topMargin + bottomMargin;
 
             int type = PRF;
             int width = hAlign.getSizeInCell(c, pWidth, cellWidth, type);
@@ -1366,10 +1353,9 @@
             String axis = horizontal ? "horizontal" : "vertical";
             int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1.
 
-            boolean changed = false;
             // We take one extra pass over traditional Bellman-Ford (and omit their final step)
             for (int i = 0; i < N; i++) {
-                changed = false;
+                boolean changed = false;
                 for (int j = 0, length = arcs.length; j < length; j++) {
                     changed |= relax(locations, arcs[j]);
                 }
@@ -1420,7 +1406,7 @@
                 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
                 Interval span = spec.span;
                 int index = leading ? span.min : span.max;
-                margins[index] = max(margins[index], getMargin(c, horizontal, leading));
+                margins[index] = max(margins[index], getMargin1(c, horizontal, leading));
             }
         }
 
@@ -1446,34 +1432,8 @@
             return trailingMargins;
         }
 
-        private void addMargins() {
-            int[] leadingMargins = getLeadingMargins();
-            int[] trailingMargins = getTrailingMargins();
-
-            int delta = 0;
-            for (int i = 0, N = getCount(); i < N; i++) {
-                int margins = leadingMargins[i] + trailingMargins[i + 1];
-                delta += margins;
-                locations[i + 1] += delta;
-            }
-        }
-
-        private int getLocationIncludingMargin(boolean leading, int index) {
-            int location = locations[index];
-            int margin;
-            if (mAlignmentMode != ALIGN_MARGINS) {
-                margin = (leading ? leadingMargins : trailingMargins)[index];
-            } else {
-                margin = 0;
-            }
-            return leading ? (location + margin) : (location - margin);
-        }
-
         private void computeLocations(int[] a) {
             solve1(getArcs(), a);
-            if (mAlignmentMode != ALIGN_MARGINS) {
-                addMargins();
-            }
         }
 
         private int[] getLocations() {
@@ -1495,12 +1455,6 @@
         }
 
         private void setParentConstraints(int min, int max) {
-            if (mAlignmentMode != ALIGN_MARGINS) {
-                int margins = sum(getLeadingMargins()) + sum(getTrailingMargins());
-                min -= margins;
-                max -= margins;
-            }
-
             parentMin.value = min;
             parentMax.value = -max;
             locationsValid = false;
@@ -1905,10 +1859,6 @@
             this.values = compact(values, index);
         }
 
-        private K getKey(int i) {
-            return keys[index[i]];
-        }
-
         private V getValue(int i) {
             return values[index[i]];
         }
@@ -1958,8 +1908,6 @@
     of the values for each View.
     */
     private static class Bounds {
-        private static final Bounds GONE = new Bounds();
-
         public int before;
         public int after;
         public int flexibility; // we're flexible iff all included specs are flexible
@@ -1995,8 +1943,8 @@
         protected final void include(View c, Spec spec, GridLayout gridLayout, Axis axis) {
             this.flexibility &= spec.getFlexibility();
             int size = gridLayout.getMeasurementIncludingMargin(c, axis.horizontal);
-            // todo test this works correctly when the returned value is UNDEFINED
             Alignment alignment = gridLayout.getAlignment(spec.alignment, axis.horizontal);
+            // todo test this works correctly when the returned value is UNDEFINED
             int before = alignment.getAlignmentValue(c, size);
             include(before, size - before);
         }
@@ -2079,6 +2027,7 @@
             if (max != interval.max) {
                 return false;
             }
+            //noinspection RedundantIfStatement
             if (min != interval.min) {
                 return false;
             }
@@ -2113,57 +2062,33 @@
      * For column groups, this specifies the horizontal alignment.
      */
     public static class Spec {
-        private static final int UNDEFINED_FLEXIBILITY = UNDEFINED;
-
         final Interval span;
-
         final Alignment alignment;
 
-        /**
-         * The <em>flexibility</em> property tells GridLayout how to derive minimum and maximum size
-         * values for a component. Specifications are made with respect to a child's
-         * 'measured size'. A child's measured size is, in turn, controlled by its
-         * height and width layout parameters which either specify a size or, in
-         * the case of {@link LayoutParams#WRAP_CONTENT WRAP_CONTENT}, defer to
-         * the computed size of the component.
-         * <p>
-         * A cell group is flexible only if <em>all</em> of its components are flexible.
-         * <p>
-         * By default, flexibility is {@link #INFLEXIBLE} only when alignment/gravity is undefined.
-         */
-        final int flexibility;
-
-        private Spec(Interval span, Alignment alignment, int flexibility) {
+        private Spec(Interval span, Alignment alignment) {
             this.span = span;
             this.alignment = alignment;
-            this.flexibility = flexibility;
         }
 
         /* Copying constructor */
         private Spec(Spec that) {
-            this(that.span, that.alignment, that.flexibility);
+            this(that.span, that.alignment);
         }
 
         private Spec(int start, int size, Alignment alignment) {
-            this(new Interval(start, start + size), alignment, UNDEFINED);
+            this(new Interval(start, start + size), alignment);
         }
 
         private Spec copyWriteSpan(Interval span) {
-            return new Spec(span, alignment, flexibility);
+            return new Spec(span, alignment);
         }
 
         private Spec copyWriteAlignment(Alignment alignment) {
-            return new Spec(span, alignment, flexibility);
-        }
-
-        private static int defaultFlexibility(Alignment alignment) {
-            return (alignment == UNDEFINED_ALIGNMENT) ? INFLEXIBLE : CAN_STRETCH;
+            return new Spec(span, alignment);
         }
 
         int getFlexibility() {
-            return (flexibility != UNDEFINED_FLEXIBILITY) ?
-                    flexibility :
-                    defaultFlexibility(alignment);
+            return (alignment == UNDEFINED_ALIGNMENT) ? INFLEXIBLE : CAN_STRETCH;
         }
 
         /**
@@ -2190,6 +2115,7 @@
             if (!alignment.equals(spec.alignment)) {
                 return false;
             }
+            //noinspection RedundantIfStatement
             if (!span.equals(spec.span)) {
                 return false;
             }
@@ -2447,15 +2373,5 @@
 
     private static final int INFLEXIBLE = 0;
 
-    /**
-     * Indicates that a view's size should be greater than or equal to the size specified by
-     * its layout parameters.
-     *
-     * @deprecated Please use {@link #spec(int, int, Alignment)} instead,
-     * all spec's that define alignment (gravity) are assumed to able to stretch.
-     *
-     * @hide
-     */
-    @Deprecated
-    public static final int CAN_STRETCH = 2;
+    private static final int CAN_STRETCH = 2;
 }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 65ee745..968d612 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -343,6 +343,9 @@
     private Drawable mSelectHandleRight;
     private Drawable mSelectHandleCenter;
 
+    // Global listener that detects changes in the global position of the TextView
+    private PositionListener mPositionListener;
+
     private float mLastDownPositionX, mLastDownPositionY;
     private Callback mCustomSelectionActionModeCallback;
 
@@ -394,7 +397,7 @@
          */
         boolean onEditorAction(TextView v, int actionId, KeyEvent event);
     }
-    
+
     public TextView(Context context) {
         this(context, null);
     }
@@ -2081,7 +2084,7 @@
                                        TextAppearance_textStyle, -1);
 
         setTypefaceByIndex(typefaceIndex, styleIndex);
-        
+
         if (appearance.getBoolean(com.android.internal.R.styleable.TextAppearance_textAllCaps,
                 false)) {
             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
@@ -3019,7 +3022,7 @@
      * To style your strings, attach android.text.style.* objects to a
      * {@link android.text.SpannableString SpannableString}, or see the
      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
-     * Available Resource Types</a> documentation for an example of setting 
+     * Available Resource Types</a> documentation for an example of setting
      * formatted text in the XML resource file.
      *
      * @attr ref android.R.styleable#TextView_text
@@ -8757,32 +8760,247 @@
         return ((minOffset >= selectionStart) && (maxOffset < selectionEnd));
     }
 
+    private PositionListener getPositionListener() {
+        if (mPositionListener == null) {
+            mPositionListener = new PositionListener();
+        }
+        return mPositionListener;
+    }
+
+    private interface TextViewPositionListener {
+        public void updatePosition(int parentPositionX, int parentPositionY, boolean modified);
+    }
+
+    private class PositionListener implements ViewTreeObserver.OnPreDrawListener {
+        // 3 handles, 2 ActionPopup (suggestionsPopup first hides the others)
+        private final int MAXIMUM_NUMBER_OF_LISTENERS = 5;
+        private TextViewPositionListener[] mPositionListeners =
+                new TextViewPositionListener[MAXIMUM_NUMBER_OF_LISTENERS];
+        private boolean mCanMove[] = new boolean[MAXIMUM_NUMBER_OF_LISTENERS];
+        private boolean mPositionHasChanged = true;
+        // Absolute position of the TextView with respect to its parent window
+        private int mPositionX, mPositionY;
+        private int mNumberOfListeners;
+
+        public void addSubscriber(TextViewPositionListener positionListener, boolean canMove) {
+            if (mNumberOfListeners == 0) {
+                updatePosition();
+                ViewTreeObserver vto = TextView.this.getViewTreeObserver();
+                vto.addOnPreDrawListener(this);
+            }
+
+            int emptySlotIndex = -1;
+            for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
+                TextViewPositionListener listener = mPositionListeners[i];
+                if (listener == positionListener) {
+                    return;
+                } else if (emptySlotIndex < 0 && listener == null) {
+                    emptySlotIndex = i;
+                }
+            }
+
+            mPositionListeners[emptySlotIndex] = positionListener;
+            mCanMove[emptySlotIndex] = canMove;
+            mNumberOfListeners++;
+        }
+
+        public void removeSubscriber(TextViewPositionListener positionListener) {
+            for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
+                if (mPositionListeners[i] == positionListener) {
+                    mPositionListeners[i] = null;
+                    mNumberOfListeners--;
+                    break;
+                }
+            }
+
+            if (mNumberOfListeners == 0) {
+                ViewTreeObserver vto = TextView.this.getViewTreeObserver();
+                vto.removeOnPreDrawListener(this);
+            }
+        }
+
+        public int getPositionX() {
+            return mPositionX;
+        }
+
+        public int getPositionY() {
+            return mPositionY;
+        }
+
+        @Override
+        public boolean onPreDraw() {
+            updatePosition();
+
+            for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
+                if (mPositionHasChanged || mCanMove[i]) {
+                    TextViewPositionListener positionListener = mPositionListeners[i];
+                    if (positionListener != null) {
+                        positionListener.updatePosition(mPositionX, mPositionY,
+                                mPositionHasChanged);
+                    }
+                }
+            }
+
+            return true;
+        }
+
+        private void updatePosition() {
+            TextView.this.getLocationInWindow(mTempCoords);
+
+            mPositionHasChanged = mTempCoords[0] != mPositionX || mTempCoords[1] != mPositionY;
+
+            mPositionX = mTempCoords[0];
+            mPositionY = mTempCoords[1];
+        }
+
+        public boolean isVisible(int positionX, int positionY) {
+            final TextView textView = TextView.this;
+
+            if (mTempRect == null) mTempRect = new Rect();
+            final Rect clip = mTempRect;
+            clip.left = getCompoundPaddingLeft();
+            clip.top = getExtendedPaddingTop();
+            clip.right = textView.getWidth() - getCompoundPaddingRight();
+            clip.bottom = textView.getHeight() - getExtendedPaddingBottom();
+
+            final ViewParent parent = textView.getParent();
+            if (parent == null || !parent.getChildVisibleRect(textView, clip, null)) {
+                return false;
+            }
+
+            int posX = mPositionX + positionX;
+            int posY = mPositionY + positionY;
+
+            // Offset by 1 to take into account 0.5 and int rounding around getPrimaryHorizontal.
+            return posX >= clip.left - 1 && posX <= clip.right + 1 &&
+                    posY >= clip.top && posY <= clip.bottom;
+        }
+
+        public boolean isOffsetVisible(int offset) {
+            final int line = mLayout.getLineForOffset(offset);
+            final int lineBottom = mLayout.getLineBottom(line);
+            final int primaryHorizontal = (int) mLayout.getPrimaryHorizontal(offset);
+            return isVisible(primaryHorizontal, lineBottom);
+        }
+    }
+
+    private abstract class PinnedPopupWindow implements TextViewPositionListener {
+        protected PopupWindow mPopupWindow;
+        protected LinearLayout mContentView;
+        int mPositionX, mPositionY;
+
+        protected abstract void createPopupWindow();
+        protected abstract void initContentView();
+        protected abstract int getTextOffset();
+        protected abstract int getVerticalLocalPosition(int line);
+        protected abstract int clipVertically(int positionY);
+
+        public PinnedPopupWindow() {
+            createPopupWindow();
+
+            mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
+            mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
+            mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
+
+            mContentView = new LinearLayout(TextView.this.getContext());
+            LayoutParams wrapContent = new LayoutParams(
+                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+            mContentView.setLayoutParams(wrapContent);
+
+            initContentView();
+            mPopupWindow.setContentView(mContentView);
+        }
+
+        public void show() {
+            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
+            mContentView.measure(
+                    View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels,
+                            View.MeasureSpec.AT_MOST),
+                    View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels,
+                            View.MeasureSpec.AT_MOST));
+
+            TextView.this.getPositionListener().addSubscriber(this, false);
+
+            computeLocalPosition();
+
+            final PositionListener positionListener = TextView.this.getPositionListener();
+            updatePosition(positionListener.getPositionX(), positionListener.getPositionY());
+        }
+
+        private void computeLocalPosition() {
+            final int offset = getTextOffset();
+
+            final int width = mContentView.getMeasuredWidth();
+            mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - width / 2.0f);
+            mPositionX += viewportToContentHorizontalOffset();
+
+            final int line = mLayout.getLineForOffset(offset);
+            mPositionY = getVerticalLocalPosition(line);
+            mPositionY += viewportToContentVerticalOffset();
+        }
+
+        private void updatePosition(int parentPositionX, int parentPositionY) {
+            int positionX = parentPositionX + mPositionX;
+            int positionY = parentPositionY + mPositionY;
+
+            positionY = clipVertically(positionY);
+
+            // Horizontal clipping
+            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
+            final int width = mContentView.getMeasuredWidth();
+            positionX = Math.min(displayMetrics.widthPixels - width, positionX);
+            positionX = Math.max(0, positionX);
+
+            if (isShowing()) {
+                mPopupWindow.update(positionX, positionY, -1, -1);
+            } else {
+                mPopupWindow.showAtLocation(TextView.this, Gravity.NO_GRAVITY,
+                        positionX, positionY);
+            }
+        }
+
+        public void hide() {
+            mPopupWindow.dismiss();
+            TextView.this.getPositionListener().removeSubscriber(this);
+        }
+
+        @Override
+        public void updatePosition(int parentPositionX, int parentPositionY, boolean modified) {
+            if (isShowing() && getPositionListener().isOffsetVisible(getTextOffset())) {
+                updatePosition(parentPositionX, parentPositionY);
+            } else {
+                hide();
+            }
+        }
+
+        public boolean isShowing() {
+            return mPopupWindow.isShowing();
+        }
+    }
+
     private static class SuggestionRangeSpan extends UnderlineSpan {
         // TODO themable, would be nice to make it a child class of TextAppearanceSpan, but
         // there is no way to have underline and TextAppearanceSpan.
     }
 
-    private class SuggestionsPopupWindow implements OnClickListener {
+    private class SuggestionsPopupWindow extends PinnedPopupWindow implements OnClickListener {
         private static final int MAX_NUMBER_SUGGESTIONS = 5;
         private static final int NO_SUGGESTIONS = -1;
-        private final PopupWindow mPopupWindow;
-        private LinearLayout mSuggestionsContainer;
         private WordIterator mSuggestionWordIterator;
         private TextAppearanceSpan[] mHighlightSpans = new TextAppearanceSpan[0];
 
-        public SuggestionsPopupWindow() {
+        @Override
+        protected void createPopupWindow() {
             mPopupWindow = new PopupWindow(TextView.this.mContext, null,
-                    com.android.internal.R.attr.textSuggestionsWindowStyle);
-            mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
+                com.android.internal.R.attr.textSuggestionsWindowStyle);
             mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
             mPopupWindow.setOutsideTouchable(true);
-            mPopupWindow.setClippingEnabled(true);
+            mPopupWindow.setClippingEnabled(false);
+        }
 
-            mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
-            mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
-
-            mSuggestionsContainer = new LinearLayout(TextView.this.mContext);
-            mSuggestionsContainer.setOrientation(LinearLayout.VERTICAL);
+        @Override
+        protected void initContentView() {
+            mContentView.setOrientation(LinearLayout.VERTICAL);
 
             LayoutInflater inflater = (LayoutInflater) TextView.this.mContext.
                     getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@@ -8795,7 +9013,7 @@
             // Inflate the suggestion items once and for all.
             for (int i = 0; i < MAX_NUMBER_SUGGESTIONS; i++) {
                 View childView = inflater.inflate(mTextEditSuggestionItemLayout,
-                        mSuggestionsContainer, false);
+                        mContentView, false);
 
                 if (! (childView instanceof TextView)) {
                     throw new IllegalArgumentException(
@@ -8803,11 +9021,9 @@
                 }
 
                 childView.setTag(new SuggestionInfo());
-                mSuggestionsContainer.addView(childView);
+                mContentView.addView(childView);
                 childView.setOnClickListener(this);
             }
-
-            mPopupWindow.setContentView(mSuggestionsContainer);
         }
 
         private class SuggestionInfo {
@@ -8827,30 +9043,61 @@
             SuggestionSpan[] suggestionSpans = spannable.getSpans(pos, pos, SuggestionSpan.class);
 
             // Cache the span length for performance reason.
-            final HashMap<SuggestionSpan, Integer> spanLengthMap =
-                new HashMap<SuggestionSpan, Integer>();
+            final HashMap<SuggestionSpan, Integer> spansLengths =
+                    new HashMap<SuggestionSpan, Integer>();
 
             for (SuggestionSpan suggestionSpan : suggestionSpans) {
                 int start = spannable.getSpanStart(suggestionSpan);
                 int end = spannable.getSpanEnd(suggestionSpan);
-                spanLengthMap.put(suggestionSpan, end - start);
+                spansLengths.put(suggestionSpan, Integer.valueOf(end - start));
             }
 
             // The suggestions are sorted according to the lenght of the text that they cover
             // (shorter first)
             Arrays.sort(suggestionSpans, new Comparator<SuggestionSpan>() {
                 public int compare(SuggestionSpan span1, SuggestionSpan span2) {
-                    return spanLengthMap.get(span1) - spanLengthMap.get(span2);
+                    return spansLengths.get(span1).intValue() - spansLengths.get(span2).intValue();
                 }
             });
 
             return suggestionSpans;
         }
 
+        @Override
         public void show() {
             if (!(mText instanceof Editable)) return;
+            updateSuggestions();
 
-            Spannable spannable = (Spannable) TextView.this.mText;
+            super.show();
+        }
+
+        @Override
+        protected int getTextOffset() {
+            return getSelectionStart();
+        }
+
+        @Override
+        protected int getVerticalLocalPosition(int line) {
+            return mLayout.getLineBottom(line);
+        }
+
+        @Override
+        protected int clipVertically(int positionY) {
+            final int height = mContentView.getMeasuredHeight();
+            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
+            return Math.min(positionY, displayMetrics.heightPixels - height);
+        }
+
+        @Override
+        public void hide() {
+            super.hide();
+            if ((mText instanceof Editable) && mSuggestionRangeSpan != null) {
+                ((Editable) mText).removeSpan(mSuggestionRangeSpan);
+            }
+        }
+
+        private void updateSuggestions() {
+            Spannable spannable = (Spannable)TextView.this.mText;
             SuggestionSpan[] suggestionSpans = getSuggestionSpans();
 
             final int nbSpans = suggestionSpans.length;
@@ -8869,7 +9116,7 @@
                 String[] suggestions = suggestionSpan.getSuggestions();
                 int nbSuggestions = suggestions.length;
                 for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) {
-                    TextView textView = (TextView) mSuggestionsContainer.getChildAt(
+                    TextView textView = (TextView) mContentView.getChildAt(
                             totalNbSuggestions);
                     textView.setText(suggestions[suggestionIndex]);
                     SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag();
@@ -8889,7 +9136,7 @@
 
             if (totalNbSuggestions == 0) {
                 // TODO Replace by final text, use a dedicated layout, add a fade out timer...
-                TextView textView = (TextView) mSuggestionsContainer.getChildAt(0);
+                TextView textView = (TextView) mContentView.getChildAt(0);
                 textView.setText("No suggestions available");
                 SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag();
                 suggestionInfo.spanStart = NO_SUGGESTIONS;
@@ -8900,26 +9147,17 @@
                         Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
 
                 for (int i = 0; i < totalNbSuggestions; i++) {
-                    final TextView textView = (TextView) mSuggestionsContainer.getChildAt(i);
+                    final TextView textView = (TextView) mContentView.getChildAt(i);
                     highlightTextDifferences(textView, spanUnionStart, spanUnionEnd);
                 }
             }
 
             for (int i = 0; i < totalNbSuggestions; i++) {
-                mSuggestionsContainer.getChildAt(i).setVisibility(VISIBLE);
+                mContentView.getChildAt(i).setVisibility(VISIBLE);
             }
             for (int i = totalNbSuggestions; i < MAX_NUMBER_SUGGESTIONS; i++) {
-                mSuggestionsContainer.getChildAt(i).setVisibility(GONE);
+                mContentView.getChildAt(i).setVisibility(GONE);
             }
-
-            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
-            final int screenWidth = displayMetrics.widthPixels;
-            final int screenHeight = displayMetrics.heightPixels;
-            mSuggestionsContainer.measure(
-                    View.MeasureSpec.makeMeasureSpec(screenWidth, View.MeasureSpec.AT_MOST),
-                    View.MeasureSpec.makeMeasureSpec(screenHeight, View.MeasureSpec.AT_MOST));
-
-            positionAtCursor();
         }
 
         private long[] getWordLimits(CharSequence text) {
@@ -9071,17 +9309,6 @@
             textView.setText(ssb);
         }
 
-        public void hide() {
-            if ((mText instanceof Editable) && mSuggestionRangeSpan != null) {
-                ((Editable) mText).removeSpan(mSuggestionRangeSpan);
-            }
-            mPopupWindow.dismiss();
-        }
-
-        public boolean isShowing() {
-            return mPopupWindow.isShowing();
-        }
-
         @Override
         public void onClick(View view) {
             if (view instanceof TextView) {
@@ -9141,44 +9368,6 @@
             }
             hide();
         }
-
-        void positionAtCursor() {
-            View contentView = mPopupWindow.getContentView();
-            int width = contentView.getMeasuredWidth();
-            int height = contentView.getMeasuredHeight();
-            final int offset = TextView.this.getSelectionStart();
-            final int line = mLayout.getLineForOffset(offset);
-            final int lineBottom = mLayout.getLineBottom(line);
-            float primaryHorizontal = mLayout.getPrimaryHorizontal(offset);
-
-            final Rect bounds = sCursorControllerTempRect;
-            bounds.left = (int) (primaryHorizontal - width / 2.0f);
-            bounds.top = lineBottom;
-
-            bounds.right = bounds.left + width;
-            bounds.bottom = bounds.top + height;
-
-            convertFromViewportToContentCoordinates(bounds);
-
-            final int[] coords = mTempCoords;
-            TextView.this.getLocationInWindow(coords);
-            coords[0] += bounds.left;
-            coords[1] += bounds.top;
-
-            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
-            final int screenHeight = displayMetrics.heightPixels;
-
-            // Vertical clipping
-            if (coords[1] + height > screenHeight) {
-                coords[1] = screenHeight - height;
-            }
-
-            // Horizontal clipping
-            coords[0] = Math.min(displayMetrics.widthPixels - width, coords[0]);
-            coords[0] = Math.max(0, coords[0]);
-
-            mPopupWindow.showAtLocation(TextView.this, Gravity.NO_GRAVITY, coords[0], coords[1]);
-        }
     }
 
     void showSuggestions() {
@@ -9359,7 +9548,7 @@
             boolean allowText = getContext().getResources().getBoolean(
                     com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon);
 
-            mode.setTitle(allowText ? 
+            mode.setTitle(allowText ?
                     mContext.getString(com.android.internal.R.string.textSelectionCABTitle) : null);
             mode.setSubtitle(null);
 
@@ -9452,29 +9641,23 @@
         }
     }
 
-    private class ActionPopupWindow implements OnClickListener {
-        private static final int TEXT_EDIT_ACTION_POPUP_TEXT =
+    private class ActionPopupWindow extends PinnedPopupWindow implements OnClickListener {
+        private static final int POPUP_TEXT_LAYOUT =
                 com.android.internal.R.layout.text_edit_action_popup_text;
-        private final PopupWindow mPopupWindow;
         private TextView mPasteTextView;
         private TextView mReplaceTextView;
-        private LinearLayout mContentView;
         // Whether or not the Paste action should be available when the action popup is displayed
         private boolean mWithPaste;
 
-        public ActionPopupWindow() {
+        @Override
+        protected void createPopupWindow() {
             mPopupWindow = new PopupWindow(TextView.this.mContext, null,
                     com.android.internal.R.attr.textSelectHandleWindowStyle);
             mPopupWindow.setClippingEnabled(true);
-            mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_PANEL);
+        }
 
-            mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
-            mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
-
-            mContentView = new LinearLayout(TextView.this.getContext());
-            LayoutParams wrapContent = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
-                    ViewGroup.LayoutParams.WRAP_CONTENT);
-            mContentView.setLayoutParams(wrapContent);
+        @Override
+        protected void initContentView() {
             mContentView.setOrientation(LinearLayout.HORIZONTAL);
             mContentView.setBackgroundResource(
                     com.android.internal.R.drawable.text_edit_side_paste_window);
@@ -9482,36 +9665,26 @@
             LayoutInflater inflater = (LayoutInflater)TextView.this.mContext.
                     getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 
-            mPasteTextView = (TextView) inflater.inflate(TEXT_EDIT_ACTION_POPUP_TEXT, null);
+            LayoutParams wrapContent = new LayoutParams(
+                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+            mPasteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
             mPasteTextView.setLayoutParams(wrapContent);
             mContentView.addView(mPasteTextView);
             mPasteTextView.setText(com.android.internal.R.string.paste);
             mPasteTextView.setOnClickListener(this);
 
-            mReplaceTextView = (TextView) inflater.inflate(TEXT_EDIT_ACTION_POPUP_TEXT, null);
+            mReplaceTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
             mReplaceTextView.setLayoutParams(wrapContent);
             mContentView.addView(mReplaceTextView);
             mReplaceTextView.setText(com.android.internal.R.string.replace);
             mReplaceTextView.setOnClickListener(this);
-
-            mPopupWindow.setContentView(mContentView);
         }
 
+        @Override
         public void show() {
             mPasteTextView.setVisibility(mWithPaste && canPaste() ? View.VISIBLE : View.GONE);
-
-            final int size = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
-            mContentView.measure(size, size);
-
-            positionAtCursor();
-        }
-
-        public void hide() {
-            mPopupWindow.dismiss();
-        }
-
-        public boolean isShowing() {
-            return mPopupWindow.isShowing();
+            super.show();
         }
 
         @Override
@@ -9524,48 +9697,30 @@
             }
         }
 
-        void positionAtCursor() {
-            int width = mContentView.getMeasuredWidth();
-            int height = mContentView.getMeasuredHeight();
-            final int selectionStart = TextView.this.getSelectionStart();
-            final int selectionEnd = TextView.this.getSelectionEnd();
-            final int offset = (selectionStart + selectionEnd) / 2;
-            final int line = mLayout.getLineForOffset(offset);
-            final int lineTop = mLayout.getLineTop(line);
-            float primaryHorizontal = mLayout.getPrimaryHorizontal(offset);
+        @Override
+        protected int getTextOffset() {
+            return (getSelectionStart() + getSelectionEnd()) / 2;
+        }
 
-            final Rect bounds = sCursorControllerTempRect;
-            bounds.left = (int) (primaryHorizontal - width / 2.0f);
-            bounds.top = lineTop - height;
+        @Override
+        protected int getVerticalLocalPosition(int line) {
+            return mLayout.getLineTop(line) - mContentView.getMeasuredHeight();
+        }
 
-            bounds.right = bounds.left + width;
-            bounds.bottom = bounds.top + height;
-
-            convertFromViewportToContentCoordinates(bounds);
-
-            final int[] coords = mTempCoords;
-            TextView.this.getLocationInWindow(coords);
-            coords[0] += bounds.left;
-            coords[1] += bounds.top;
-
-            // Vertical clipping, move under edited line and to the side of insertion cursor
-            if (coords[1] < 0) {
-                coords[1] += height;
-                final int lineBottom = mLayout.getLineBottom(line);
-                final int lineHeight = lineBottom - lineTop;
-                coords[1] += lineHeight;
+        @Override
+        protected int clipVertically(int positionY) {
+            if (positionY < 0) {
+                final int offset = getTextOffset();
+                final int line = mLayout.getLineForOffset(offset);
+                positionY += mLayout.getLineBottom(line) - mLayout.getLineTop(line);
+                positionY += mContentView.getMeasuredHeight();
 
                 // Assumes insertion and selection handles share the same height
                 final Drawable handle = mContext.getResources().getDrawable(mTextSelectHandleRes);
-                coords[1] += handle.getIntrinsicHeight();
+                positionY += handle.getIntrinsicHeight();
             }
 
-            // Horizontal clipping
-            coords[0] = Math.max(0, coords[0]);
-            final int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels;
-            coords[0] = Math.min(screenWidth - width, coords[0]);
-
-            mPopupWindow.showAtLocation(TextView.this, Gravity.NO_GRAVITY, coords[0], coords[1]);
+            return positionY;
         }
 
         public void setShowWithPaste(boolean withPaste) {
@@ -9573,7 +9728,7 @@
         }
     }
 
-    private abstract class HandleView extends View implements ViewTreeObserver.OnPreDrawListener {
+    private abstract class HandleView extends View implements TextViewPositionListener {
         protected Drawable mDrawable;
         private final PopupWindow mContainer;
         // Position with respect to the parent TextView
@@ -9581,21 +9736,19 @@
         private boolean mIsDragging;
         // Offset from touch position to mPosition
         private float mTouchToWindowOffsetX, mTouchToWindowOffsetY;
-        protected float mHotspotX;
+        protected int mHotspotX;
         // Offsets the hotspot point up, so that cursor is not hidden by the finger when moving up
         private float mTouchOffsetY;
         // Where the touch position should be on the handle to ensure a maximum cursor visibility
         private float mIdealVerticalOffset;
         // Parent's (TextView) previous position in window
         private int mLastParentX, mLastParentY;
-        // PopupWindow container absolute position with respect to the enclosing window
-        private int mContainerPositionX, mContainerPositionY;
-        // Visible or not (scrolled off screen), whether or not this handle should be visible
-        private boolean mIsActive = false;
-        // Used to detect that setFrame was called
-        private boolean mNeedsUpdate = true;
         // Transient action popup window for Paste and Replace actions
         protected ActionPopupWindow mActionPopupWindow;
+        // Previous text character offset
+        private int mPreviousOffset = -1;
+        // Previous text character offset
+        private boolean mPositionHasChanged = true;
         // Used to delay the appearance of the action popup window
         private Runnable mActionPopupShower;
 
@@ -9615,15 +9768,6 @@
             mIdealVerticalOffset = 0.7f * handleHeight;
         }
 
-        @Override
-        protected boolean setFrame(int left, int top, int right, int bottom) {
-            boolean changed = super.setFrame(left, top, right, bottom);
-            // onPreDraw is called for PhoneWindow before the layout of this view is
-            // performed. Make sure to update position, even if container didn't move.
-            if (changed) mNeedsUpdate  = true;
-            return changed;
-        }
-
         protected abstract void initDrawable();
 
         // Touch-up filter: number of previous positions remembered
@@ -9641,12 +9785,6 @@
         }
 
         private void addPositionToTouchUpFilter(int offset) {
-            if (mNumberPreviousOffsets > 0 &&
-                    mPreviousOffsets[mPreviousOffsetIndex] == offset) {
-                // Make sure only actual changes of position are recorded.
-                return;
-            }
-
             mPreviousOffsetIndex = (mPreviousOffsetIndex + 1) % HISTORY_SIZE;
             mPreviousOffsets[mPreviousOffsetIndex] = offset;
             mPreviousOffsetsTimes[mPreviousOffsetIndex] = SystemClock.uptimeMillis();
@@ -9665,28 +9803,28 @@
 
             if (i > 0 && i < iMax &&
                     (now - mPreviousOffsetsTimes[index]) > TOUCH_UP_FILTER_DELAY_BEFORE) {
-                updateOffset(mPreviousOffsets[index]);
+                positionAtCursorOffset(mPreviousOffsets[index]);
             }
         }
 
+        public boolean offsetHasBeenChanged() {
+            return mNumberPreviousOffsets > 1;
+        }
+
         @Override
         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
             setMeasuredDimension(mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
         }
 
         public void show() {
-            if (isShowing()) {
-                mContainer.update(mContainerPositionX, mContainerPositionY, -1, -1);
-            } else {
-                mContainer.showAtLocation(TextView.this, 0,
-                        mContainerPositionX, mContainerPositionY);
+            if (isShowing()) return;
 
-                if (!mIsActive) {
-                    ViewTreeObserver vto = TextView.this.getViewTreeObserver();
-                    vto.addOnPreDrawListener(this);
-                    mIsActive = true;
-                }
-            }
+            getPositionListener().addSubscriber(this, true);
+
+            // Make sure the offset is always considered new, even when focusing at same position
+            mPreviousOffset = -1;
+            positionAtCursorOffset(getCurrentCursorOffset());
+
             hideActionPopupWindow();
         }
 
@@ -9699,9 +9837,7 @@
         public void hide() {
             dismiss();
 
-            ViewTreeObserver vto = TextView.this.getViewTreeObserver();
-            vto.removeOnPreDrawListener(this);
-            mIsActive = false;
+            TextView.this.getPositionListener().removeSubscriber(this);
         }
 
         void showActionPopupWindow(int delay, boolean withPaste) {
@@ -9734,7 +9870,7 @@
             return mContainer.isShowing();
         }
 
-        private boolean isPositionVisible() {
+        private boolean isVisible() {
             // Always show a dragging handle.
             if (mIsDragging) {
                 return true;
@@ -9744,103 +9880,71 @@
                 return false;
             }
 
-            final int extendedPaddingTop = getExtendedPaddingTop();
-            final int extendedPaddingBottom = getExtendedPaddingBottom();
-            final int compoundPaddingLeft = getCompoundPaddingLeft();
-            final int compoundPaddingRight = getCompoundPaddingRight();
-
-            final TextView textView = TextView.this;
-
-            if (mTempRect == null) mTempRect = new Rect();
-            final Rect clip = mTempRect;
-            clip.left = compoundPaddingLeft;
-            clip.top = extendedPaddingTop;
-            clip.right = textView.getWidth() - compoundPaddingRight;
-            clip.bottom = textView.getHeight() - extendedPaddingBottom;
-
-            final ViewParent parent = textView.getParent();
-            if (parent == null || !parent.getChildVisibleRect(textView, clip, null)) {
-                return false;
-            }
-
-            final int[] coords = mTempCoords;
-            textView.getLocationInWindow(coords);
-            final int posX = coords[0] + mPositionX + (int) mHotspotX;
-            final int posY = coords[1] + mPositionY;
-
-            // Offset by 1 to take into account 0.5 and int rounding around getPrimaryHorizontal.
-            return posX >= clip.left - 1 && posX <= clip.right + 1 &&
-                    posY >= clip.top && posY <= clip.bottom;
+            return getPositionListener().isVisible(mPositionX + mHotspotX, mPositionY);
         }
 
         public abstract int getCurrentCursorOffset();
 
-        public abstract void updateOffset(int offset);
+        public abstract void updateSelection(int offset);
 
         public abstract void updatePosition(float x, float y);
 
         protected void positionAtCursorOffset(int offset) {
-            // A HandleView relies on the layout, which may be nulled by external methods.
+            // A HandleView relies on the layout, which may be nulled by external methods
             if (mLayout == null) {
                 // Will update controllers' state, hiding them and stopping selection mode if needed
                 prepareCursorControllers();
                 return;
             }
 
-            addPositionToTouchUpFilter(offset);
-            final int line = mLayout.getLineForOffset(offset);
-            final int lineBottom = mLayout.getLineBottom(line);
+            if (offset != mPreviousOffset) {
+                updateSelection(offset);
+                addPositionToTouchUpFilter(offset);
+                final int line = mLayout.getLineForOffset(offset);
 
-            mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX);
-            mPositionY = lineBottom;
+                mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX);
+                mPositionY = mLayout.getLineBottom(line);
 
-            // Take TextView's padding into account.
-            mPositionX += viewportToContentHorizontalOffset();
-            mPositionY += viewportToContentVerticalOffset();
+                // Take TextView's padding into account.
+                mPositionX += viewportToContentHorizontalOffset();
+                mPositionY += viewportToContentVerticalOffset();
+
+                mPreviousOffset = offset;
+                mPositionHasChanged = true;
+            }
         }
 
-        private void checkForContainerPositionChange() {
-            positionAtCursorOffset(getCurrentCursorOffset());
-
-            final int previousContainerPositionX = mContainerPositionX;
-            final int previousContainerPositionY = mContainerPositionY;
-
-            TextView.this.getLocationInWindow(mTempCoords);
-            mContainerPositionX = mTempCoords[0] + mPositionX;
-            mContainerPositionY = mTempCoords[1] + mPositionY;
-
-            mNeedsUpdate |= previousContainerPositionX != mContainerPositionX;
-            mNeedsUpdate |= previousContainerPositionY != mContainerPositionY;
-        }
-
-        public boolean onPreDraw() {
-            checkForContainerPositionChange();
-            if (mNeedsUpdate) {
+        public void updatePosition(int parentPositionX, int parentPositionY, boolean modified) {
+            if (modified || mPositionHasChanged) {
                 if (mIsDragging) {
-                    if (mTempCoords[0] != mLastParentX || mTempCoords[1] != mLastParentY) {
-                        mTouchToWindowOffsetX += mTempCoords[0] - mLastParentX;
-                        mTouchToWindowOffsetY += mTempCoords[1] - mLastParentY;
-                        mLastParentX = mTempCoords[0];
-                        mLastParentY = mTempCoords[1];
+                    // Update touchToWindow offset in case of parent scrolling while dragging
+                    if (parentPositionX != mLastParentX || parentPositionY != mLastParentY) {
+                        mTouchToWindowOffsetX += parentPositionX - mLastParentX;
+                        mTouchToWindowOffsetY += parentPositionY - mLastParentY;
+                        mLastParentX = parentPositionX;
+                        mLastParentY = parentPositionY;
                     }
 
                     onHandleMoved();
                 }
 
-                if (isPositionVisible()) {
-                    mContainer.update(mContainerPositionX, mContainerPositionY, -1, -1);
-
-                    if (mIsActive && !isShowing()) {
-                        show();
+                if (isVisible()) {
+                    final int positionX = parentPositionX + mPositionX;
+                    final int positionY = parentPositionY + mPositionY;
+                    if (isShowing()) {
+                        mContainer.update(positionX, positionY, -1, -1);
+                    } else {
+                        mContainer.showAtLocation(TextView.this, Gravity.NO_GRAVITY,
+                                positionX, positionY);
                     }
                 } else {
                     if (isShowing()) {
                         dismiss();
                     }
                 }
-                mNeedsUpdate = false;
+
+                mPositionHasChanged = false;
             }
-            return true;
         }
 
         @Override
@@ -9857,10 +9961,9 @@
                     mTouchToWindowOffsetX = ev.getRawX() - mPositionX;
                     mTouchToWindowOffsetY = ev.getRawY() - mPositionY;
 
-                    final int[] coords = mTempCoords;
-                    TextView.this.getLocationInWindow(coords);
-                    mLastParentX = coords[0];
-                    mLastParentY = coords[1];
+                    final PositionListener positionListener = getPositionListener();
+                    mLastParentX = positionListener.getPositionX();
+                    mLastParentY = positionListener.getPositionY();
                     mIsDragging = true;
                     break;
                 }
@@ -9963,7 +10066,7 @@
                         mTextSelectHandleRes);
             }
             mDrawable = mSelectHandleCenter;
-            mHotspotX = mDrawable.getIntrinsicWidth() / 2.0f;
+            mHotspotX = mDrawable.getIntrinsicWidth() / 2;
         }
 
         @Override
@@ -9977,15 +10080,17 @@
                     break;
 
                 case MotionEvent.ACTION_UP:
-                    final float deltaX = mDownPositionX - ev.getRawX();
-                    final float deltaY = mDownPositionY - ev.getRawY();
-                    final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
-                    if (distanceSquared < mSquaredTouchSlopDistance) {
-                        if (mActionPopupWindow != null && mActionPopupWindow.isShowing()) {
-                            // Tapping on the handle dismisses the displayed action popup
-                            mActionPopupWindow.hide();
-                        } else {
-                            show(0);
+                    if (!offsetHasBeenChanged()) {
+                        final float deltaX = mDownPositionX - ev.getRawX();
+                        final float deltaY = mDownPositionY - ev.getRawY();
+                        final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
+                        if (distanceSquared < mSquaredTouchSlopDistance) {
+                            if (mActionPopupWindow != null && mActionPopupWindow.isShowing()) {
+                                // Tapping on the handle dismisses the displayed action popup
+                                mActionPopupWindow.hide();
+                            } else {
+                                show(0);
+                            }
                         }
                     }
                     hideAfterDelay();
@@ -10008,13 +10113,13 @@
         }
 
         @Override
-        public void updateOffset(int offset) {
+        public void updateSelection(int offset) {
             Selection.setSelection((Spannable) mText, offset);
         }
 
         @Override
         public void updatePosition(float x, float y) {
-            updateOffset(getOffsetForPosition(x, y));
+            positionAtCursorOffset(getOffsetForPosition(x, y));
         }
 
         @Override
@@ -10038,7 +10143,7 @@
                         mTextSelectHandleLeftRes);
             }
             mDrawable = mSelectHandleLeft;
-            mHotspotX = mDrawable.getIntrinsicWidth() * 3.0f / 4.0f;
+            mHotspotX = (mDrawable.getIntrinsicWidth() * 3) / 4;
         }
 
         @Override
@@ -10047,7 +10152,7 @@
         }
 
         @Override
-        public void updateOffset(int offset) {
+        public void updateSelection(int offset) {
             Selection.setSelection((Spannable) mText, offset, getSelectionEnd());
         }
 
@@ -10063,7 +10168,7 @@
             // Handles can not cross and selection is at least one character
             if (offset >= selectionEnd) offset = selectionEnd - 1;
 
-            Selection.setSelection((Spannable) mText, offset, selectionEnd);
+            positionAtCursorOffset(offset);
         }
 
         public ActionPopupWindow getActionPopupWindow() {
@@ -10079,7 +10184,7 @@
                         mTextSelectHandleRightRes);
             }
             mDrawable = mSelectHandleRight;
-            mHotspotX = mDrawable.getIntrinsicWidth() / 4.0f;
+            mHotspotX = mDrawable.getIntrinsicWidth() / 4;
         }
 
         @Override
@@ -10088,7 +10193,7 @@
         }
 
         @Override
-        public void updateOffset(int offset) {
+        public void updateSelection(int offset) {
             Selection.setSelection((Spannable) mText, getSelectionStart(), offset);
         }
 
@@ -10104,7 +10209,7 @@
             // Handles can not cross and selection is at least one character
             if (offset <= selectionStart) offset = selectionStart + 1;
 
-            Selection.setSelection((Spannable) mText, selectionStart, offset);
+            positionAtCursorOffset(offset);
         }
 
         public void setActionPopupWindow(ActionPopupWindow actionPopupWindow) {
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index 7839a08..5e70e4c 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -150,6 +150,11 @@
 
     private CopyOnWriteArrayList<WeakReference<MenuPresenter>> mPresenters =
             new CopyOnWriteArrayList<WeakReference<MenuPresenter>>();
+
+    /**
+     * Currently expanded menu item; must be collapsed when we clear.
+     */
+    private MenuItemImpl mExpandedItem;
     
     /**
      * Called by menu to notify of close and selection changes.
@@ -512,6 +517,9 @@
     }
     
     public void clear() {
+        if (mExpandedItem != null) {
+            collapseItemActionView(mExpandedItem);
+        }
         mItems.clear();
         
         onItemsChanged(true);
@@ -1223,11 +1231,14 @@
         }
         startDispatchingItemsChanged();
 
+        if (expanded) {
+            mExpandedItem = item;
+        }
         return expanded;
     }
 
     public boolean collapseItemActionView(MenuItemImpl item) {
-        if (mPresenters.isEmpty()) return false;
+        if (mPresenters.isEmpty() || mExpandedItem != item) return false;
 
         boolean collapsed = false;
 
@@ -1242,6 +1253,9 @@
         }
         startDispatchingItemsChanged();
 
+        if (collapsed) {
+            mExpandedItem = null;
+        }
         return collapsed;
     }
 }
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 446c842..4878b0f 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -1265,9 +1265,8 @@
         @Override
         public void initForMenu(Context context, MenuBuilder menu) {
             // Clear the expanded action view when menus change.
-            mExpandedActionView = null;
-            if (mCurrentExpandedItem != null) {
-                mCurrentExpandedItem.collapseActionView();
+            if (mMenu != null && mCurrentExpandedItem != null) {
+                mMenu.collapseItemActionView(mCurrentExpandedItem);
             }
             mMenu = menu;
         }
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 5aa47b7..ed2d3b0 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -95,19 +95,6 @@
         <item name="windowExitAnimation">@anim/dialog_exit</item>
     </style>
 
-    <!-- Standard animations for hiding and showing the status bar. -->
-    <style name="Animation.StatusBar">
-        <item name="windowEnterAnimation">@anim/status_bar_enter</item>
-        <item name="windowExitAnimation">@anim/status_bar_exit</item>
-    </style>
-
-    <!-- {@hide} -->
-    <style name="Animation.StatusBar.IntruderAlert"
-        parent="@android:style/Animation.StatusBar">
-        <item name="android:windowEnterAnimation">@anim/priority_alert_enter</item>
-        <item name="android:windowExitAnimation">@anim/priority_alert_exit</item>
-    </style>
-
     <!-- Standard animations for a translucent window or activity.  This
          style is <em>not<em> used by default for the translucent theme
          (since translucent activities are a special case that have no
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 3e786c3..cd55b0e 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -187,7 +187,7 @@
         AudioSystem.STREAM_RING,  // STREAM_RING
         AudioSystem.STREAM_MUSIC, // STREAM_MUSIC
         AudioSystem.STREAM_ALARM,  // STREAM_ALARM
-        AudioSystem.STREAM_NOTIFICATION,  // STREAM_NOTIFICATION
+        AudioSystem.STREAM_RING,   // STREAM_NOTIFICATION
         AudioSystem.STREAM_BLUETOOTH_SCO, // STREAM_BLUETOOTH_SCO
         AudioSystem.STREAM_SYSTEM,  // STREAM_SYSTEM_ENFORCED
         AudioSystem.STREAM_VOICE_CALL, // STREAM_DTMF
@@ -242,9 +242,6 @@
      */
     private int mVibrateSetting;
 
-    /** @see System#NOTIFICATIONS_USE_RING_VOLUME */
-    private int mNotificationsUseRingVolume;
-
     // Broadcast receiver for device connections intent broadcasts
     private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
 
@@ -456,16 +453,6 @@
                 System.MUTE_STREAMS_AFFECTED,
                 ((1 << AudioSystem.STREAM_MUSIC)|(1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_SYSTEM)));
 
-        if (mVoiceCapable) {
-            mNotificationsUseRingVolume = System.getInt(cr,
-                    Settings.System.NOTIFICATIONS_USE_RING_VOLUME, 1);
-        } else {
-            mNotificationsUseRingVolume = 1;
-        }
-
-        if (mNotificationsUseRingVolume == 1) {
-            STREAM_VOLUME_ALIAS[AudioSystem.STREAM_NOTIFICATION] = AudioSystem.STREAM_RING;
-        }
         // Each stream will read its own persisted settings
 
         // Broadcast the sticky intent
@@ -2202,8 +2189,6 @@
             super(new Handler());
             mContentResolver.registerContentObserver(Settings.System.getUriFor(
                 Settings.System.MODE_RINGER_STREAMS_AFFECTED), false, this);
-            mContentResolver.registerContentObserver(Settings.System.getUriFor(
-                    Settings.System.NOTIFICATIONS_USE_RING_VOLUME), false, this);
         }
 
         @Override
@@ -2227,29 +2212,6 @@
                     mRingerModeAffectedStreams = ringerModeAffectedStreams;
                     setRingerModeInt(getRingerMode(), false);
                 }
-
-                int notificationsUseRingVolume = Settings.System.getInt(mContentResolver,
-                        Settings.System.NOTIFICATIONS_USE_RING_VOLUME,
-                        1);
-                if (mVoiceCapable) {
-                    if (notificationsUseRingVolume != mNotificationsUseRingVolume) {
-                        mNotificationsUseRingVolume = notificationsUseRingVolume;
-                        if (mNotificationsUseRingVolume == 1) {
-                            STREAM_VOLUME_ALIAS[AudioSystem.STREAM_NOTIFICATION] = AudioSystem.STREAM_RING;
-                            mStreamStates[AudioSystem.STREAM_NOTIFICATION].setVolumeIndexSettingName(
-                                    System.VOLUME_SETTINGS[AudioSystem.STREAM_RING]);
-                        } else {
-                            STREAM_VOLUME_ALIAS[AudioSystem.STREAM_NOTIFICATION] = AudioSystem.STREAM_NOTIFICATION;
-                            mStreamStates[AudioSystem.STREAM_NOTIFICATION].setVolumeIndexSettingName(
-                                    System.VOLUME_SETTINGS[AudioSystem.STREAM_NOTIFICATION]);
-                            // Persist notification volume volume as it was not persisted while aliased to ring volume
-                            //  and persist with no delay as there might be registered observers of the persisted
-                            //  notification volume.
-                            sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, AudioSystem.STREAM_NOTIFICATION,
-                                    SENDMSG_REPLACE, 1, 1, mStreamStates[AudioSystem.STREAM_NOTIFICATION], 0);
-                        }
-                    }
-                }
             }
         }
     }
diff --git a/media/libstagefright/AACExtractor.cpp b/media/libstagefright/AACExtractor.cpp
index 4203b6e..a5a6b64 100644
--- a/media/libstagefright/AACExtractor.cpp
+++ b/media/libstagefright/AACExtractor.cpp
@@ -92,7 +92,7 @@
     size_t frameSize = 0;
 
     uint8_t syncword[2];
-    if (source->readAt(0, &syncword, 2) != 2) {
+    if (source->readAt(offset, &syncword, 2) != 2) {
         return 0;
     }
     if ((syncword[0] != 0xff) || ((syncword[1] & 0xf6) != 0xf0)) {
diff --git a/core/res/res/anim/priority_alert_enter.xml b/packages/SystemUI/res/anim/priority_alert_enter.xml
similarity index 100%
rename from core/res/res/anim/priority_alert_enter.xml
rename to packages/SystemUI/res/anim/priority_alert_enter.xml
diff --git a/core/res/res/anim/priority_alert_exit.xml b/packages/SystemUI/res/anim/priority_alert_exit.xml
similarity index 100%
rename from core/res/res/anim/priority_alert_exit.xml
rename to packages/SystemUI/res/anim/priority_alert_exit.xml
diff --git a/core/res/res/anim/status_bar_enter.xml b/packages/SystemUI/res/anim/status_bar_enter.xml
similarity index 87%
rename from core/res/res/anim/status_bar_enter.xml
rename to packages/SystemUI/res/anim/status_bar_enter.xml
index 1a1dc9b..f1c1301 100644
--- a/core/res/res/anim/status_bar_enter.xml
+++ b/packages/SystemUI/res/anim/status_bar_enter.xml
@@ -18,7 +18,8 @@
 */
 -->
 
-<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@interpolator/decelerate_quad">
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@android:interpolator/decelerate_quad">
 	<translate android:fromYDelta="-75%" android:toYDelta="0"
         android:duration="@android:integer/config_mediumAnimTime"/>
 	<alpha android:fromAlpha="0.0" android:toAlpha="1.0"
diff --git a/core/res/res/anim/status_bar_exit.xml b/packages/SystemUI/res/anim/status_bar_exit.xml
similarity index 88%
rename from core/res/res/anim/status_bar_exit.xml
rename to packages/SystemUI/res/anim/status_bar_exit.xml
index 1f71090..46462e2 100644
--- a/core/res/res/anim/status_bar_exit.xml
+++ b/packages/SystemUI/res/anim/status_bar_exit.xml
@@ -18,7 +18,8 @@
 */
 -->
 
-<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@interpolator/accelerate_quad">
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@android:interpolator/accelerate_quad">
 	<translate android:fromYDelta="0" android:toYDelta="-75%"
         android:startOffset="100" android:duration="@android:integer/config_mediumAnimTime"/>
 	<alpha android:fromAlpha="1.0" android:toAlpha="0.0"
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 91a8855..ad236b7 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -52,4 +52,15 @@
         <item name="android:windowExitAnimation">@*android:anim/shrink_fade_out_from_bottom</item>
     </style>
 
+    <!-- Standard animations for hiding and showing the status bar. -->
+    <style name="Animation.StatusBar">
+        <item name="android:windowEnterAnimation">@anim/status_bar_enter</item>
+        <item name="android:windowExitAnimation">@anim/status_bar_exit</item>
+    </style>
+
+    <style name="Animation.StatusBar.IntruderAlert">
+        <item name="android:windowEnterAnimation">@anim/priority_alert_enter</item>
+        <item name="android:windowExitAnimation">@anim/priority_alert_exit</item>
+    </style>
+
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java
index 918d5a3..61da1f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java
@@ -119,7 +119,8 @@
                 PixelFormat.RGBX_8888);
         lp.gravity = getStatusBarGravity();
         lp.setTitle("StatusBar");
-        // TODO lp.windowAnimations = R.style.Animation_StatusBar;
+        lp.packageName = mContext.getPackageName();
+        lp.windowAnimations = R.style.Animation_StatusBar;
         WindowManagerImpl.getDefault().addView(sb, lp);
 
         if (SPEW) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index d25a827..7bb8277 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -505,7 +505,8 @@
         lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
         lp.y += height * 1.5; // FIXME
         lp.setTitle("IntruderAlert");
-        lp.windowAnimations = com.android.internal.R.style.Animation_StatusBar_IntruderAlert;
+        lp.packageName = mContext.getPackageName();
+        lp.windowAnimations = R.style.Animation_StatusBar_IntruderAlert;
 
         WindowManagerImpl.getDefault().addView(mIntruderAlertView, lp);
     }
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 9c19da2..14f7c11 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -552,6 +552,8 @@
             if (!st.shownPanelView.hasFocus()) {
                 st.shownPanelView.requestFocus();
             }
+        } else if (!st.isInExpandedMode) {
+            width = MATCH_PARENT;
         }
 
         st.isOpen = true;
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index ec45530..2355d5c 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -51,6 +51,8 @@
 
 #include <media/EffectsFactoryApi.h>
 #include <audio_effects/effect_visualizer.h>
+#include <audio_effects/effect_ns.h>
+#include <audio_effects/effect_aec.h>
 
 #include <cpustats/ThreadCpuUsage.h>
 #include <powermanager/PowerManager.h>
@@ -148,7 +150,8 @@
 
 AudioFlinger::AudioFlinger()
     : BnAudioFlinger(),
-        mPrimaryHardwareDev(0), mMasterVolume(1.0f), mMasterMute(false), mNextUniqueId(1)
+        mPrimaryHardwareDev(0), mMasterVolume(1.0f), mMasterMute(false), mNextUniqueId(1),
+        mBtNrec(false)
 {
 }
 
@@ -717,6 +720,31 @@
             final_result = result ?: final_result;
         }
         mHardwareStatus = AUDIO_HW_IDLE;
+        // disable AEC and NS if the device is a BT SCO headset supporting those pre processings
+        AudioParameter param = AudioParameter(keyValuePairs);
+        String8 value;
+        if (param.get(String8(AUDIO_PARAMETER_KEY_BT_NREC), value) == NO_ERROR) {
+            Mutex::Autolock _l(mLock);
+            bool btNrec = (value == AUDIO_PARAMETER_VALUE_ON);
+            if (mBtNrec != btNrec) {
+                for (size_t i = 0; i < mRecordThreads.size(); i++) {
+                    sp<RecordThread> thread = mRecordThreads.valueAt(i);
+                    RecordThread::RecordTrack *track = thread->track();
+                    if (track != NULL) {
+                        audio_devices_t device = (audio_devices_t)(
+                                thread->device() & AUDIO_DEVICE_IN_ALL);
+                        bool suspend = audio_is_bluetooth_sco_device(device) && btNrec;
+                        thread->setEffectSuspended(FX_IID_AEC,
+                                                   suspend,
+                                                   track->sessionId());
+                        thread->setEffectSuspended(FX_IID_NS,
+                                                   suspend,
+                                                   track->sessionId());
+                    }
+                }
+                mBtNrec = btNrec;
+            }
+        }
         return final_result;
     }
 
@@ -1130,6 +1158,140 @@
     LOGW("power manager service died !!!");
 }
 
+void AudioFlinger::ThreadBase::setEffectSuspended(
+        const effect_uuid_t *type, bool suspend, int sessionId)
+{
+    Mutex::Autolock _l(mLock);
+    setEffectSuspended_l(type, suspend, sessionId);
+}
+
+void AudioFlinger::ThreadBase::setEffectSuspended_l(
+        const effect_uuid_t *type, bool suspend, int sessionId)
+{
+    sp<EffectChain> chain;
+    chain = getEffectChain_l(sessionId);
+    if (chain != 0) {
+        if (type != NULL) {
+            chain->setEffectSuspended_l(type, suspend);
+        } else {
+            chain->setEffectSuspendedAll_l(suspend);
+        }
+    }
+
+    updateSuspendedSessions_l(type, suspend, sessionId);
+}
+
+void AudioFlinger::ThreadBase::checkSuspendOnAddEffectChain_l(const sp<EffectChain>& chain)
+{
+    int index = mSuspendedSessions.indexOfKey(chain->sessionId());
+    if (index < 0) {
+        return;
+    }
+
+    KeyedVector <int, sp<SuspendedSessionDesc> > sessionEffects =
+            mSuspendedSessions.editValueAt(index);
+
+    for (size_t i = 0; i < sessionEffects.size(); i++) {
+        sp <SuspendedSessionDesc> desc = sessionEffects.valueAt(i);
+        for (int j = 0; j < desc->mRefCount; j++) {
+            if (sessionEffects.keyAt(i) == EffectChain::kKeyForSuspendAll) {
+                chain->setEffectSuspendedAll_l(true);
+            } else {
+                LOGV("checkSuspendOnAddEffectChain_l() suspending effects %08x",
+                     desc->mType.timeLow);
+                chain->setEffectSuspended_l(&desc->mType, true);
+            }
+        }
+    }
+}
+
+void AudioFlinger::ThreadBase::updateSuspendedSessionsOnRemoveEffectChain_l(
+        const sp<EffectChain>& chain)
+{
+    int index = mSuspendedSessions.indexOfKey(chain->sessionId());
+    if (index < 0) {
+        return;
+    }
+    LOGV("updateSuspendedSessionsOnRemoveEffectChain_l() removed suspended session %d",
+         chain->sessionId());
+    mSuspendedSessions.removeItemsAt(index);
+}
+
+void AudioFlinger::ThreadBase::updateSuspendedSessions_l(const effect_uuid_t *type,
+                                                         bool suspend,
+                                                         int sessionId)
+{
+    int index = mSuspendedSessions.indexOfKey(sessionId);
+
+    KeyedVector <int, sp<SuspendedSessionDesc> > sessionEffects;
+
+    if (suspend) {
+        if (index >= 0) {
+            sessionEffects = mSuspendedSessions.editValueAt(index);
+        } else {
+            mSuspendedSessions.add(sessionId, sessionEffects);
+        }
+    } else {
+        if (index < 0) {
+            return;
+        }
+        sessionEffects = mSuspendedSessions.editValueAt(index);
+    }
+
+
+    int key = EffectChain::kKeyForSuspendAll;
+    if (type != NULL) {
+        key = type->timeLow;
+    }
+    index = sessionEffects.indexOfKey(key);
+
+    sp <SuspendedSessionDesc> desc;
+    if (suspend) {
+        if (index >= 0) {
+            desc = sessionEffects.valueAt(index);
+        } else {
+            desc = new SuspendedSessionDesc();
+            if (type != NULL) {
+                memcpy(&desc->mType, type, sizeof(effect_uuid_t));
+            }
+            sessionEffects.add(key, desc);
+            LOGV("updateSuspendedSessions_l() suspend adding effect %08x", key);
+        }
+        desc->mRefCount++;
+    } else {
+        if (index < 0) {
+            return;
+        }
+        desc = sessionEffects.valueAt(index);
+        if (--desc->mRefCount == 0) {
+            LOGV("updateSuspendedSessions_l() restore removing effect %08x", key);
+            sessionEffects.removeItemsAt(index);
+            if (sessionEffects.isEmpty()) {
+                LOGV("updateSuspendedSessions_l() restore removing session %d",
+                                 sessionId);
+                mSuspendedSessions.removeItem(sessionId);
+            }
+        }
+    }
+    if (!sessionEffects.isEmpty()) {
+        mSuspendedSessions.replaceValueFor(sessionId, sessionEffects);
+    }
+}
+
+void AudioFlinger::ThreadBase::checkSuspendOnEffectEnabled(const sp<EffectModule>& effect,
+                                                            bool enabled,
+                                                            int sessionId)
+{
+    Mutex::Autolock _l(mLock);
+
+    // TODO: implement PlaybackThread or RecordThread specific behavior here
+
+    sp<EffectChain> chain = getEffectChain_l(sessionId);
+    if (chain != 0) {
+        chain->checkSuspendOnEffectEnabled(effect, enabled);
+    }
+}
+
 // ----------------------------------------------------------------------------
 
 AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinger,
@@ -4143,7 +4305,11 @@
         }
 
         mTrack = track.get();
-
+        // disable AEC and NS if the device is a BT SCO headset supporting those pre processings
+        bool suspend = audio_is_bluetooth_sco_device(
+                (audio_devices_t)(mDevice & AUDIO_DEVICE_IN_ALL)) && mAudioFlinger->btNrec();
+        setEffectSuspended_l(FX_IID_AEC, suspend, sessionId);
+        setEffectSuspended_l(FX_IID_NS, suspend, sessionId);
     }
     lStatus = NO_ERROR;
 
@@ -4363,6 +4529,13 @@
                 status = BAD_VALUE;
             } else {
                 mDevice &= (uint32_t)~(value & AUDIO_DEVICE_IN_ALL);
+                // disable AEC and NS if the device is a BT SCO headset supporting those pre processings
+                if (mTrack != NULL) {
+                    bool suspend = audio_is_bluetooth_sco_device(
+                            (audio_devices_t)value) && mAudioFlinger->btNrec();
+                    setEffectSuspended_l(FX_IID_AEC, suspend, mTrack->sessionId());
+                    setEffectSuspended_l(FX_IID_NS, suspend, mTrack->sessionId());
+                }
             }
             mDevice |= (uint32_t)value;
         }
@@ -4490,6 +4663,12 @@
     return result;
 }
 
+AudioFlinger::RecordThread::RecordTrack* AudioFlinger::RecordThread::track()
+{
+    Mutex::Autolock _l(mLock);
+    return mTrack;
+}
+
 // ----------------------------------------------------------------------------
 
 int AudioFlinger::openOutput(uint32_t *pDevices,
@@ -4874,10 +5053,6 @@
 }
 
 
-// this UUID must match the one defined in media/libeffects/EffectVisualizer.cpp
-static const effect_uuid_t VISUALIZATION_UUID_ =
-    {0xd069d9e0, 0x8329, 0x11df, 0x9168, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}};
-
 sp<IEffect> AudioFlinger::createEffect(pid_t pid,
         effect_descriptor_t *pDesc,
         const sp<IEffectClient>& effectClient,
@@ -4915,14 +5090,6 @@
         goto Exit;
     }
 
-    // check recording permission for visualizer
-    if ((memcmp(&pDesc->type, SL_IID_VISUALIZATION, sizeof(effect_uuid_t)) == 0 ||
-         memcmp(&pDesc->uuid, &VISUALIZATION_UUID_, sizeof(effect_uuid_t)) == 0) &&
-        !recordingAllowed()) {
-        lStatus = PERMISSION_DENIED;
-        goto Exit;
-    }
-
     if (io == 0) {
         if (sessionId == AUDIO_SESSION_OUTPUT_STAGE) {
             // output must be specified by AudioPolicyManager when using session
@@ -5003,6 +5170,13 @@
             goto Exit;
         }
 
+        // check recording permission for visualizer
+        if ((memcmp(&desc.type, SL_IID_VISUALIZATION, sizeof(effect_uuid_t)) == 0) &&
+            !recordingAllowed()) {
+            lStatus = PERMISSION_DENIED;
+            goto Exit;
+        }
+
         // return effect descriptor
         memcpy(pDesc, &desc, sizeof(effect_descriptor_t));
 
@@ -5069,10 +5243,10 @@
     return handle;
 }
 
-status_t AudioFlinger::moveEffects(int session, int srcOutput, int dstOutput)
+status_t AudioFlinger::moveEffects(int sessionId, int srcOutput, int dstOutput)
 {
     LOGV("moveEffects() session %d, srcOutput %d, dstOutput %d",
-            session, srcOutput, dstOutput);
+            sessionId, srcOutput, dstOutput);
     Mutex::Autolock _l(mLock);
     if (srcOutput == dstOutput) {
         LOGW("moveEffects() same dst and src outputs %d", dstOutput);
@@ -5091,24 +5265,24 @@
 
     Mutex::Autolock _dl(dstThread->mLock);
     Mutex::Autolock _sl(srcThread->mLock);
-    moveEffectChain_l(session, srcThread, dstThread, false);
+    moveEffectChain_l(sessionId, srcThread, dstThread, false);
 
     return NO_ERROR;
 }
 
 // moveEffectChain_l mustbe called with both srcThread and dstThread mLocks held
-status_t AudioFlinger::moveEffectChain_l(int session,
+status_t AudioFlinger::moveEffectChain_l(int sessionId,
                                    AudioFlinger::PlaybackThread *srcThread,
                                    AudioFlinger::PlaybackThread *dstThread,
                                    bool reRegister)
 {
     LOGV("moveEffectChain_l() session %d from thread %p to thread %p",
-            session, srcThread, dstThread);
+            sessionId, srcThread, dstThread);
 
-    sp<EffectChain> chain = srcThread->getEffectChain_l(session);
+    sp<EffectChain> chain = srcThread->getEffectChain_l(sessionId);
     if (chain == 0) {
         LOGW("moveEffectChain_l() effect chain for session %d not on source thread %p",
-                session, srcThread);
+                sessionId, srcThread);
         return INVALID_OPERATION;
     }
 
@@ -5143,7 +5317,7 @@
             AudioSystem::registerEffect(&effect->desc(),
                                         dstOutput,
                                         strategy,
-                                        session,
+                                        sessionId,
                                         effect->id());
         }
         effect = chain->getEffectFromId_l(0);
@@ -5385,6 +5559,7 @@
 
 void AudioFlinger::ThreadBase::disconnectEffect(const sp<EffectModule>& effect,
                                                     const wp<EffectHandle>& handle) {
+
     Mutex::Autolock _l(mLock);
     LOGV("disconnectEffect() %p effect %p", this, effect.get());
     // delete the effect module if removing last handle on it
@@ -5451,6 +5626,7 @@
         if (mEffectChains[i]->sessionId() < session) break;
     }
     mEffectChains.insertAt(chain, i);
+    checkSuspendOnAddEffectChain_l(chain);
 
     return NO_ERROR;
 }
@@ -5463,6 +5639,7 @@
 
     for (size_t i = 0; i < mEffectChains.size(); i++) {
         if (chain == mEffectChains[i]) {
+            updateSuspendedSessionsOnRemoveEffectChain_l(chain);
             mEffectChains.removeAt(i);
             // detach all active tracks from the chain
             for (size_t i = 0 ; i < mActiveTracks.size() ; ++i) {
@@ -5540,6 +5717,8 @@
     chain->setInBuffer(NULL);
     chain->setOutBuffer(NULL);
 
+    checkSuspendOnAddEffectChain_l(chain);
+
     mEffectChains.add(chain);
 
     return NO_ERROR;
@@ -5552,6 +5731,7 @@
             "removeEffectChain_l() %p invalid chain size %d on thread %p",
             chain.get(), mEffectChains.size(), this);
     if (mEffectChains.size() == 1) {
+        updateSuspendedSessionsOnRemoveEffectChain_l(chain);
         mEffectChains.removeAt(0);
     }
     return 0;
@@ -5570,7 +5750,7 @@
                                         int id,
                                         int sessionId)
     : mThread(wThread), mChain(chain), mId(id), mSessionId(sessionId), mEffectInterface(NULL),
-      mStatus(NO_INIT), mState(IDLE)
+      mStatus(NO_INIT), mState(IDLE), mSuspended(false)
 {
     LOGV("Constructor %p", this);
     int lStatus;
@@ -5634,14 +5814,17 @@
     }
     // if inserted in first place, move effect control from previous owner to this handle
     if (i == 0) {
+        bool enabled = false;
         if (h != 0) {
-            h->setControl(false, true);
+            enabled = h->enabled();
+            h->setControl(false/*hasControl*/, true /*signal*/, enabled /*enabled*/);
         }
-        handle->setControl(true, false);
+        handle->setControl(true /*hasControl*/, false /*signal*/, enabled /*enabled*/);
         status = NO_ERROR;
     } else {
         status = ALREADY_EXISTS;
     }
+    LOGV("addHandle() %p added handle %p in position %d", this, handle.get(), i);
     mHandles.insertAt(handle, i);
     return status;
 }
@@ -5657,13 +5840,21 @@
     if (i == size) {
         return size;
     }
+    LOGV("removeHandle() %p removed handle %p in position %d", this, handle.unsafe_get(), i);
+
+    bool enabled = false;
+    EffectHandle *hdl = handle.unsafe_get();
+    if (hdl) {
+        LOGV("removeHandle() unsafe_get OK");
+        enabled = hdl->enabled();
+    }
     mHandles.removeAt(i);
     size = mHandles.size();
     // if removed from first place, move effect control from this handle to next in line
     if (i == 0 && size != 0) {
         sp<EffectHandle> h = mHandles[0].promote();
         if (h != 0) {
-            h->setControl(true, true);
+            h->setControl(true /*hasControl*/, true /*signal*/ , enabled /*enabled*/);
         }
     }
 
@@ -5677,8 +5868,21 @@
     return size;
 }
 
+sp<AudioFlinger::EffectHandle> AudioFlinger::EffectModule::controlHandle()
+{
+    Mutex::Autolock _l(mLock);
+    sp<EffectHandle> handle;
+    if (mHandles.size() != 0) {
+        handle = mHandles[0].promote();
+    }
+    return handle;
+}
+
+
+
 void AudioFlinger::EffectModule::disconnect(const wp<EffectHandle>& handle)
 {
+    LOGV("disconnect() %p handle %p ", this, handle.unsafe_get());
     // keep a strong reference on this EffectModule to avoid calling the
     // destructor before we exit
     sp<EffectModule> keep(this);
@@ -6139,6 +6343,17 @@
     return status;
 }
 
+void AudioFlinger::EffectModule::setSuspended(bool suspended)
+{
+    Mutex::Autolock _l(mLock);
+    mSuspended = suspended;
+}
+bool AudioFlinger::EffectModule::suspended()
+{
+    Mutex::Autolock _l(mLock);
+    return mSuspended;
+}
+
 status_t AudioFlinger::EffectModule::dump(int fd, const Vector<String16>& args)
 {
     const size_t SIZE = 256;
@@ -6235,7 +6450,8 @@
                                         const sp<IEffectClient>& effectClient,
                                         int32_t priority)
     : BnEffect(),
-    mEffect(effect), mEffectClient(effectClient), mClient(client), mPriority(priority), mHasControl(false)
+    mEffect(effect), mEffectClient(effectClient), mClient(client),
+    mPriority(priority), mHasControl(false), mEnabled(false)
 {
     LOGV("constructor %p", this);
 
@@ -6258,30 +6474,66 @@
 {
     LOGV("Destructor %p", this);
     disconnect();
+    LOGV("Destructor DONE %p", this);
 }
 
 status_t AudioFlinger::EffectHandle::enable()
 {
+    LOGV("enable %p", this);
     if (!mHasControl) return INVALID_OPERATION;
     if (mEffect == 0) return DEAD_OBJECT;
 
+    mEnabled = true;
+
+    sp<ThreadBase> thread = mEffect->thread().promote();
+    if (thread != 0) {
+        thread->checkSuspendOnEffectEnabled(mEffect, true, mEffect->sessionId());
+    }
+
+    // checkSuspendOnEffectEnabled() can suspend this same effect when enabled
+    if (mEffect->suspended()) {
+        return NO_ERROR;
+    }
+
     return mEffect->setEnabled(true);
 }
 
 status_t AudioFlinger::EffectHandle::disable()
 {
+    LOGV("disable %p", this);
     if (!mHasControl) return INVALID_OPERATION;
-    if (mEffect == NULL) return DEAD_OBJECT;
+    if (mEffect == 0) return DEAD_OBJECT;
 
-    return mEffect->setEnabled(false);
+    mEnabled = false;
+
+    if (mEffect->suspended()) {
+        return NO_ERROR;
+    }
+
+    status_t status = mEffect->setEnabled(false);
+
+    sp<ThreadBase> thread = mEffect->thread().promote();
+    if (thread != 0) {
+        thread->checkSuspendOnEffectEnabled(mEffect, false, mEffect->sessionId());
+    }
+
+    return status;
 }
 
 void AudioFlinger::EffectHandle::disconnect()
 {
+    LOGV("disconnect %p", this);
     if (mEffect == 0) {
         return;
     }
+
     mEffect->disconnect(this);
+
+    sp<ThreadBase> thread = mEffect->thread().promote();
+    if (thread != 0) {
+        thread->checkSuspendOnEffectEnabled(mEffect, false, mEffect->sessionId());
+    }
+
     // release sp on module => module destructor can be called now
     mEffect.clear();
     if (mCblk) {
@@ -6373,11 +6625,13 @@
     return mCblkMemory;
 }
 
-void AudioFlinger::EffectHandle::setControl(bool hasControl, bool signal)
+void AudioFlinger::EffectHandle::setControl(bool hasControl, bool signal, bool enabled)
 {
     LOGV("setControl %p control %d", this, hasControl);
 
     mHasControl = hasControl;
+    mEnabled = enabled;
+
     if (signal && mEffectClient != 0) {
         mEffectClient->controlStatusChanged(hasControl);
     }
@@ -6448,7 +6702,7 @@
 
 }
 
-// getEffectFromDesc_l() must be called with PlaybackThread::mLock held
+// getEffectFromDesc_l() must be called with ThreadBase::mLock held
 sp<AudioFlinger::EffectModule> AudioFlinger::EffectChain::getEffectFromDesc_l(effect_descriptor_t *descriptor)
 {
     sp<EffectModule> effect;
@@ -6463,7 +6717,7 @@
     return effect;
 }
 
-// getEffectFromId_l() must be called with PlaybackThread::mLock held
+// getEffectFromId_l() must be called with ThreadBase::mLock held
 sp<AudioFlinger::EffectModule> AudioFlinger::EffectChain::getEffectFromId_l(int id)
 {
     sp<EffectModule> effect;
@@ -6479,6 +6733,22 @@
     return effect;
 }
 
+// getEffectFromType_l() must be called with ThreadBase::mLock held
+sp<AudioFlinger::EffectModule> AudioFlinger::EffectChain::getEffectFromType_l(
+        const effect_uuid_t *type)
+{
+    sp<EffectModule> effect;
+    size_t size = mEffects.size();
+
+    for (size_t i = 0; i < size; i++) {
+        if (memcmp(&mEffects[i]->desc().type, type, sizeof(effect_uuid_t)) == 0) {
+            effect = mEffects[i];
+            break;
+        }
+    }
+    return effect;
+}
+
 // Must be called with EffectChain::mLock locked
 void AudioFlinger::EffectChain::process_l()
 {
@@ -6773,6 +7043,166 @@
     return NO_ERROR;
 }
 
+// must be called with ThreadBase::mLock held
+void AudioFlinger::EffectChain::setEffectSuspended_l(
+        const effect_uuid_t *type, bool suspend)
+{
+    sp<SuspendedEffectDesc> desc;
+    // use effect type UUID timelow as key as there is no real risk of identical
+    // timeLow fields among effect type UUIDs.
+    int index = mSuspendedEffects.indexOfKey(type->timeLow);
+    if (suspend) {
+        if (index >= 0) {
+            desc = mSuspendedEffects.valueAt(index);
+        } else {
+            desc = new SuspendedEffectDesc();
+            memcpy(&desc->mType, type, sizeof(effect_uuid_t));
+            mSuspendedEffects.add(type->timeLow, desc);
+            LOGV("setEffectSuspended_l() add entry for %08x", type->timeLow);
+        }
+        if (desc->mRefCount++ == 0) {
+            sp<EffectModule> effect = getEffectIfEnabled(type);
+            if (effect != 0) {
+                desc->mEffect = effect;
+                effect->setSuspended(true);
+                effect->setEnabled(false);
+            }
+        }
+    } else {
+        if (index < 0) {
+            return;
+        }
+        desc = mSuspendedEffects.valueAt(index);
+        if (desc->mRefCount <= 0) {
+            LOGW("setEffectSuspended_l() restore refcount should not be 0 %d", desc->mRefCount);
+            desc->mRefCount = 1;
+        }
+        if (--desc->mRefCount == 0) {
+            LOGV("setEffectSuspended_l() remove entry for %08x", mSuspendedEffects.keyAt(index));
+            if (desc->mEffect != 0) {
+                sp<EffectModule> effect = desc->mEffect.promote();
+                if (effect != 0) {
+                    effect->setSuspended(false);
+                    sp<EffectHandle> handle = effect->controlHandle();
+                    if (handle != 0) {
+                        effect->setEnabled(handle->enabled());
+                    }
+                }
+                desc->mEffect.clear();
+            }
+            mSuspendedEffects.removeItemsAt(index);
+        }
+    }
+}
+
+// must be called with ThreadBase::mLock held
+void AudioFlinger::EffectChain::setEffectSuspendedAll_l(bool suspend)
+{
+    sp<SuspendedEffectDesc> desc;
+
+    int index = mSuspendedEffects.indexOfKey((int)kKeyForSuspendAll);
+    if (suspend) {
+        if (index >= 0) {
+            desc = mSuspendedEffects.valueAt(index);
+        } else {
+            desc = new SuspendedEffectDesc();
+            mSuspendedEffects.add((int)kKeyForSuspendAll, desc);
+            LOGV("setEffectSuspendedAll_l() add entry for 0");
+        }
+        if (desc->mRefCount++ == 0) {
+            Vector< sp<EffectModule> > effects = getSuspendEligibleEffects();
+            for (size_t i = 0; i < effects.size(); i++) {
+                setEffectSuspended_l(&effects[i]->desc().type, true);
+            }
+        }
+    } else {
+        if (index < 0) {
+            return;
+        }
+        desc = mSuspendedEffects.valueAt(index);
+        if (desc->mRefCount <= 0) {
+            LOGW("setEffectSuspendedAll_l() restore refcount should not be 0 %d", desc->mRefCount);
+            desc->mRefCount = 1;
+        }
+        if (--desc->mRefCount == 0) {
+            Vector<const effect_uuid_t *> types;
+            for (size_t i = 0; i < mSuspendedEffects.size(); i++) {
+                if (mSuspendedEffects.keyAt(i) == (int)kKeyForSuspendAll) {
+                    continue;
+                }
+                types.add(&mSuspendedEffects.valueAt(i)->mType);
+            }
+            for (size_t i = 0; i < types.size(); i++) {
+                setEffectSuspended_l(types[i], false);
+            }
+            LOGV("setEffectSuspendedAll_l() remove entry for %08x", mSuspendedEffects.keyAt(index));
+            mSuspendedEffects.removeItem((int)kKeyForSuspendAll);
+        }
+    }
+}
+
+Vector< sp<AudioFlinger::EffectModule> > AudioFlinger::EffectChain::getSuspendEligibleEffects()
+{
+    Vector< sp<EffectModule> > effects;
+    for (size_t i = 0; i < mEffects.size(); i++) {
+        effect_descriptor_t desc = mEffects[i]->desc();
+        // auxiliary effects and vizualizer are never suspended on output mix
+        if ((mSessionId == AUDIO_SESSION_OUTPUT_MIX) && (
+            ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) ||
+             (memcmp(&desc.type, SL_IID_VISUALIZATION, sizeof(effect_uuid_t)) == 0))) {
+            continue;
+        }
+        effects.add(mEffects[i]);
+    }
+    return effects;
+}
+
+sp<AudioFlinger::EffectModule> AudioFlinger::EffectChain::getEffectIfEnabled(
+                                                            const effect_uuid_t *type)
+{
+    sp<EffectModule> effect;
+    effect = getEffectFromType_l(type);
+    if (effect != 0 && !effect->isEnabled()) {
+        effect.clear();
+    }
+    return effect;
+}
+
+void AudioFlinger::EffectChain::checkSuspendOnEffectEnabled(const sp<EffectModule>& effect,
+                                                            bool enabled)
+{
+    int index = mSuspendedEffects.indexOfKey(effect->desc().type.timeLow);
+    if (enabled) {
+        if (index < 0) {
+            // if the effect is not suspend check if all effects are suspended
+            index = mSuspendedEffects.indexOfKey((int)kKeyForSuspendAll);
+            if (index < 0) {
+                return;
+            }
+            setEffectSuspended_l(&effect->desc().type, enabled);
+            index = mSuspendedEffects.indexOfKey(effect->desc().type.timeLow);
+        }
+        LOGV("checkSuspendOnEffectEnabled() enable suspending fx %08x",
+             effect->desc().type.timeLow);
+        sp<SuspendedEffectDesc> desc = mSuspendedEffects.valueAt(index);
+        // if effect is requested to suspended but was not yet enabled, supend it now.
+        if (desc->mEffect == 0) {
+            desc->mEffect = effect;
+            effect->setEnabled(false);
+            effect->setSuspended(true);
+        }
+    } else {
+        if (index < 0) {
+            return;
+        }
+        LOGV("checkSuspendOnEffectEnabled() disable restoring fx %08x",
+             effect->desc().type.timeLow);
+        sp<SuspendedEffectDesc> desc = mSuspendedEffects.valueAt(index);
+        desc->mEffect.clear();
+        effect->setSuspended(false);
+    }
+}
+
 #undef LOG_TAG
 #define LOG_TAG "AudioFlinger"
 
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 7b6215f..791341a 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -165,7 +165,7 @@
                         int *id,
                         int *enabled);
 
-    virtual status_t moveEffects(int session, int srcOutput, int dstOutput);
+    virtual status_t moveEffects(int sessionId, int srcOutput, int dstOutput);
 
     enum hardware_call_state {
         AUDIO_HW_IDLE = 0,
@@ -206,6 +206,8 @@
 
                 uint32_t    getMode() { return mMode; }
 
+                bool        btNrec() { return mBtNrec; }
+
 private:
                             AudioFlinger();
     virtual                 ~AudioFlinger();
@@ -477,14 +479,45 @@
                     // strategy is only meaningful for PlaybackThread which implements this method
                     virtual uint32_t getStrategyForSession_l(int sessionId) { return 0; }
 
+                    // suspend or restore effect according to the type of effect passed. a NULL
+                    // type pointer means suspend all effects in the session
+                    void setEffectSuspended(const effect_uuid_t *type,
+                                            bool suspend,
+                                            int sessionId = AUDIO_SESSION_OUTPUT_MIX);
+                    // check if some effects must be suspended/restored when an effect is enabled
+                    // or disabled
+        virtual     void checkSuspendOnEffectEnabled(const sp<EffectModule>& effect,
+                                                     bool enabled,
+                                                     int sessionId = AUDIO_SESSION_OUTPUT_MIX);
+
         mutable     Mutex                   mLock;
 
     protected:
 
+                    // entry describing an effect being suspended in mSuspendedSessions keyed vector
+                    class SuspendedSessionDesc : public RefBase {
+                    public:
+                        SuspendedSessionDesc() : mRefCount(0) {}
+
+                        int mRefCount;          // number of active suspend requests
+                        effect_uuid_t mType;    // effect type UUID
+                    };
+
                     void        acquireWakeLock();
                     void        acquireWakeLock_l();
                     void        releaseWakeLock();
                     void        releaseWakeLock_l();
+                    void setEffectSuspended_l(const effect_uuid_t *type,
+                                              bool suspend,
+                                              int sessionId = AUDIO_SESSION_OUTPUT_MIX);
+                    // updated mSuspendedSessions when an effect suspended or restored
+                    void        updateSuspendedSessions_l(const effect_uuid_t *type,
+                                                          bool suspend,
+                                                          int sessionId);
+                    // check if some effects must be suspended when an effect chain is added
+                    void checkSuspendOnAddEffectChain_l(const sp<EffectChain>& chain);
+                    // updated mSuspendedSessions when an effect chain is removed
+                    void updateSuspendedSessionsOnRemoveEffectChain_l(const sp<EffectChain>& chain);
 
         friend class Track;
         friend class TrackBase;
@@ -519,6 +552,9 @@
                     sp<IPowerManager>       mPowerManager;
                     sp<IBinder>             mWakeLockToken;
                     sp<PMDeathRecipient>    mDeathRecipient;
+                    // list of suspended effects per session and per type. The first vector is
+                    // keyed by session ID, the second by type UUID timeLow field
+                    KeyedVector< int, KeyedVector< int, sp<SuspendedSessionDesc> > >  mSuspendedSessions;
     };
 
     // --- PlaybackThread ---
@@ -848,7 +884,7 @@
               void audioConfigChanged_l(int event, int ioHandle, void *param2);
 
               uint32_t nextUniqueId();
-              status_t moveEffectChain_l(int session,
+              status_t moveEffectChain_l(int sessionId,
                                      AudioFlinger::PlaybackThread *srcThread,
                                      AudioFlinger::PlaybackThread *dstThread,
                                      bool reRegister);
@@ -908,6 +944,7 @@
                     bool        setOverflow() { bool tmp = mOverflow; mOverflow = true; return tmp; }
 
                     void        dump(char* buffer, size_t size);
+
         private:
             friend class AudioFlinger;
             friend class RecordThread;
@@ -950,8 +987,6 @@
                 AudioStreamIn* getInput() { return mInput; }
                 virtual audio_stream_t* stream() { return &mInput->stream->common; }
 
-
-                void        setTrack(RecordTrack *recordTrack) { mTrack = recordTrack; }
         virtual status_t    getNextBuffer(AudioBufferProvider::Buffer* buffer);
         virtual void        releaseBuffer(AudioBufferProvider::Buffer* buffer);
         virtual bool        checkForNewParameters_l();
@@ -963,6 +998,7 @@
         virtual status_t addEffectChain_l(const sp<EffectChain>& chain);
         virtual size_t removeEffectChain_l(const sp<EffectChain>& chain);
         virtual uint32_t hasAudioSession(int sessionId);
+                RecordTrack* track();
 
     private:
                 RecordThread();
@@ -1059,6 +1095,7 @@
         int16_t     *outBuffer() { return mConfig.outputCfg.buffer.s16; }
         void        setChain(const wp<EffectChain>& chain) { mChain = chain; }
         void        setThread(const wp<ThreadBase>& thread) { mThread = thread; }
+        wp<ThreadBase>& thread() { return mThread; }
 
         status_t addHandle(sp<EffectHandle>& handle);
         void disconnect(const wp<EffectHandle>& handle);
@@ -1071,6 +1108,10 @@
         status_t         setVolume(uint32_t *left, uint32_t *right, bool controller);
         status_t         setMode(uint32_t mode);
         status_t         stop();
+        void             setSuspended(bool suspended);
+        bool             suspended();
+
+        sp<EffectHandle> controlHandle();
 
         status_t         dump(int fd, const Vector<String16>& args);
 
@@ -1099,6 +1140,7 @@
         uint32_t mMaxDisableWaitCnt;    // maximum grace period before forcing an effect off after
                                         // sending disable command.
         uint32_t mDisableWaitCnt;       // current process() calls count during disable period.
+        bool     mSuspended;            // effect is suspended: temporarily disabled by framework
     };
 
     // The EffectHandle class implements the IEffect interface. It provides resources
@@ -1131,13 +1173,17 @@
 
 
         // Give or take control of effect module
-        void setControl(bool hasControl, bool signal);
+        // - hasControl: true if control is given, false if removed
+        // - signal: true client app should be signaled of change, false otherwise
+        // - enabled: state of the effect when control is passed
+        void setControl(bool hasControl, bool signal, bool enabled);
         void commandExecuted(uint32_t cmdCode,
                              uint32_t cmdSize,
                              void *pCmdData,
                              uint32_t replySize,
                              void *pReplyData);
         void setEnabled(bool enabled);
+        bool enabled() { return mEnabled; }
 
         // Getters
         int id() { return mEffect->id(); }
@@ -1160,6 +1206,8 @@
         uint8_t*            mBuffer;        // pointer to parameter area in shared memory
         int mPriority;                      // client application priority to control the effect
         bool mHasControl;                   // true if this handle is controlling the effect
+        bool mEnabled;                      // cached enable state: needed when the effect is
+                                            // restored after being suspended
     };
 
     // the EffectChain class represents a group of effects associated to one audio session.
@@ -1174,6 +1222,10 @@
         EffectChain(const wp<ThreadBase>& wThread, int sessionId);
         ~EffectChain();
 
+        // special key used for an entry in mSuspendedEffects keyed vector
+        // corresponding to a suspend all request.
+        static const int        kKeyForSuspendAll = 0;
+
         void process_l();
 
         void lock() {
@@ -1191,6 +1243,7 @@
 
         sp<EffectModule> getEffectFromDesc_l(effect_descriptor_t *descriptor);
         sp<EffectModule> getEffectFromId_l(int id);
+        sp<EffectModule> getEffectFromType_l(const effect_uuid_t *type);
         bool setVolume_l(uint32_t *left, uint32_t *right);
         void setDevice_l(uint32_t device);
         void setMode_l(uint32_t mode);
@@ -1221,6 +1274,15 @@
         void setStrategy(uint32_t strategy)
                  { mStrategy = strategy; }
 
+        // suspend effect of the given type
+        void setEffectSuspended_l(const effect_uuid_t *type,
+                                  bool suspend);
+        // suspend all eligible effects
+        void setEffectSuspendedAll_l(bool suspend);
+        // check if effects should be suspend or restored when a given effect is enable or disabled
+        virtual void checkSuspendOnEffectEnabled(const sp<EffectModule>& effect,
+                                              bool enabled);
+
         status_t dump(int fd, const Vector<String16>& args);
 
     protected:
@@ -1228,6 +1290,21 @@
         EffectChain(const EffectChain&);
         EffectChain& operator =(const EffectChain&);
 
+        class SuspendedEffectDesc : public RefBase {
+        public:
+            SuspendedEffectDesc() : mRefCount(0) {}
+
+            int mRefCount;
+            effect_uuid_t mType;
+            wp<EffectModule> mEffect;
+        };
+
+        // get a list of effect modules to suspend when an effect of the type
+        // passed is enabled.
+        Vector< sp<EffectModule> > getSuspendEligibleEffects();
+        // get an effect module if it is currently enable
+        sp<EffectModule> getEffectIfEnabled(const effect_uuid_t *type);
+
         wp<ThreadBase> mThread;     // parent mixer thread
         Mutex mLock;                // mutex protecting effect list
         Vector<sp<EffectModule> > mEffects; // list of effect modules
@@ -1243,6 +1320,10 @@
         uint32_t mNewLeftVolume;       // new volume on left channel
         uint32_t mNewRightVolume;      // new volume on right channel
         uint32_t mStrategy; // strategy for this effect chain
+        // mSuspendedEffects lists all effect currently suspended in the chain
+        // use effect type UUID timelow field as key. There is no real risk of identical
+        // timeLow fields among effect type UUIDs.
+        KeyedVector< int, sp<SuspendedEffectDesc> > mSuspendedEffects;
     };
 
     struct AudioStreamOut {
@@ -1283,7 +1364,8 @@
 
                 DefaultKeyedVector< pid_t, sp<NotificationClient> >    mNotificationClients;
                 volatile int32_t                    mNextUniqueId;
-                uint32_t mMode;
+                uint32_t                            mMode;
+                bool                                mBtNrec;
 
 };
 
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 1bbe934..bf9e014 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -40,6 +40,7 @@
 import android.net.NetworkConfig;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkQuotaInfo;
 import android.net.NetworkState;
 import android.net.NetworkStateTracker;
 import android.net.NetworkUtils;
@@ -737,6 +738,30 @@
         return result.toArray(new NetworkState[result.size()]);
     }
 
+    private NetworkState getNetworkStateUnchecked(int networkType) {
+        if (isNetworkTypeValid(networkType)) {
+            final NetworkStateTracker tracker = mNetTrackers[networkType];
+            if (tracker != null) {
+                return new NetworkState(tracker.getNetworkInfo(), tracker.getLinkProperties(),
+                        tracker.getLinkCapabilities());
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public NetworkQuotaInfo getActiveNetworkQuotaInfo() {
+        enforceAccessPermission();
+        final NetworkState state = getNetworkStateUnchecked(mActiveDefaultNetwork);
+        if (state != null) {
+            try {
+                return mPolicyManager.getNetworkQuotaInfo(state);
+            } catch (RemoteException e) {
+            }
+        }
+        return null;
+    }
+
     public boolean setRadios(boolean turnOn) {
         boolean result = true;
         enforceChangePermission();
diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java
index 88d94c2..fed554c 100644
--- a/services/java/com/android/server/NativeDaemonConnector.java
+++ b/services/java/com/android/server/NativeDaemonConnector.java
@@ -19,6 +19,9 @@
 import android.net.LocalSocketAddress;
 import android.net.LocalSocket;
 import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.Slog;
@@ -39,7 +42,7 @@
  * daemon which uses the libsysutils FrameworkListener
  * protocol.
  */
-final class NativeDaemonConnector implements Runnable {
+final class NativeDaemonConnector implements Runnable, Handler.Callback {
     private static final boolean LOCAL_LOGD = false;
 
     private BlockingQueue<String> mResponseQueue;
@@ -47,6 +50,7 @@
     private String                TAG = "NativeDaemonConnector";
     private String                mSocket;
     private INativeDaemonConnectorCallbacks mCallbacks;
+    private Handler               mCallbackHandler;
 
     private final int BUFFER_SIZE = 4096;
 
@@ -76,7 +80,11 @@
         mResponseQueue = new LinkedBlockingQueue<String>(responseQueueSize);
     }
 
+    @Override
     public void run() {
+        HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
+        thread.start();
+        mCallbackHandler = new Handler(thread.getLooper(), this);
 
         while (true) {
             try {
@@ -88,6 +96,21 @@
         }
     }
 
+    @Override
+    public boolean handleMessage(Message msg) {
+        String event = (String) msg.obj;
+        try {
+            if (!mCallbacks.onEvent(msg.what, event, event.split(" "))) {
+                Slog.w(TAG, String.format(
+                        "Unhandled event '%s'", event));
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, String.format(
+                    "Error handling '%s'", event), e);
+        }
+        return true;
+    }
+
     private void listenToSocket() throws IOException {
         LocalSocket socket = null;
 
@@ -119,20 +142,13 @@
                         String event = new String(buffer, start, i - start);
                         if (LOCAL_LOGD) Slog.d(TAG, String.format("RCV <- {%s}", event));
 
-                        String[] tokens = event.split(" ");
+                        String[] tokens = event.split(" ", 2);
                         try {
                             int code = Integer.parseInt(tokens[0]);
 
                             if (code >= ResponseCode.UnsolicitedInformational) {
-                                try {
-                                    if (!mCallbacks.onEvent(code, event, tokens)) {
-                                        Slog.w(TAG, String.format(
-                                                "Unhandled event (%s)", event));
-                                    }
-                                } catch (Exception ex) {
-                                    Slog.e(TAG, String.format(
-                                            "Error handling '%s'", event), ex);
-                                }
+                                mCallbackHandler.sendMessage(
+                                        mCallbackHandler.obtainMessage(code, event));
                             } else {
                                 try {
                                     mResponseQueue.put(event);
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index 756cd00..a075255 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.net;
 
+import static android.Manifest.permission.ACCESS_NETWORK_STATE;
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
 import static android.Manifest.permission.DUMP;
 import static android.Manifest.permission.MANAGE_APP_TOKENS;
@@ -75,9 +76,11 @@
 import android.net.INetworkStatsService;
 import android.net.NetworkIdentity;
 import android.net.NetworkPolicy;
+import android.net.NetworkQuotaInfo;
 import android.net.NetworkState;
 import android.net.NetworkStats;
 import android.net.NetworkTemplate;
+import android.os.Binder;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -1054,6 +1057,7 @@
         synchronized (mRulesLock) {
             mRestrictBackground = restrictBackground;
             updateRulesForRestrictBackgroundLocked();
+            writePolicyLocked();
         }
     }
 
@@ -1066,6 +1070,68 @@
         }
     }
 
+    private NetworkPolicy findPolicyForNetworkLocked(NetworkIdentity ident) {
+        for (NetworkPolicy policy : mNetworkPolicy.values()) {
+            if (policy.template.matches(ident)) {
+                return policy;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public NetworkQuotaInfo getNetworkQuotaInfo(NetworkState state) {
+        mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
+
+        // only returns usage summary, so we don't require caller to have
+        // READ_NETWORK_USAGE_HISTORY.
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return getNetworkQuotaInfoUnchecked(state);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private NetworkQuotaInfo getNetworkQuotaInfoUnchecked(NetworkState state) {
+        final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state);
+
+        final NetworkPolicy policy;
+        synchronized (mRulesLock) {
+            policy = findPolicyForNetworkLocked(ident);
+        }
+
+        if (policy == null) {
+            // missing policy means we can't derive useful quota info
+            return null;
+        }
+
+        final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
+                : System.currentTimeMillis();
+
+        final long start = computeLastCycleBoundary(currentTime, policy);
+        final long end = currentTime;
+
+        // find total bytes used under policy
+        long totalBytes = 0;
+        try {
+            final NetworkStats stats = mNetworkStats.getSummaryForNetwork(
+                    policy.template, start, end);
+            final NetworkStats.Entry entry = stats.getValues(0, null);
+            totalBytes = entry.rxBytes + entry.txBytes;
+        } catch (RemoteException e) {
+            Slog.w(TAG, "problem reading summary for template " + policy.template);
+        }
+
+        // report soft and hard limits under policy
+        final long softLimitBytes = policy.warningBytes != WARNING_DISABLED ? policy.warningBytes
+                : NetworkQuotaInfo.NO_LIMIT;
+        final long hardLimitBytes = policy.limitBytes != LIMIT_DISABLED ? policy.limitBytes
+                : NetworkQuotaInfo.NO_LIMIT;
+
+        return new NetworkQuotaInfo(totalBytes, softLimitBytes, hardLimitBytes);
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
         mContext.enforceCallingOrSelfPermission(DUMP, TAG);