Merge "Implement limited shared libraries in apks." into jb-mr2-dev
diff --git a/api/current.txt b/api/current.txt
index 3132a91..4eb6a0b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10046,16 +10046,20 @@
     field public static final int TYPE_ACCELEROMETER = 1; // 0x1
     field public static final int TYPE_ALL = -1; // 0xffffffff
     field public static final int TYPE_AMBIENT_TEMPERATURE = 13; // 0xd
+    field public static final int TYPE_GAME_ROTATION_VECTOR = 15; // 0xf
     field public static final int TYPE_GRAVITY = 9; // 0x9
     field public static final int TYPE_GYROSCOPE = 4; // 0x4
+    field public static final int TYPE_GYROSCOPE_UNCALIBRATED = 16; // 0x10
     field public static final int TYPE_LIGHT = 5; // 0x5
     field public static final int TYPE_LINEAR_ACCELERATION = 10; // 0xa
     field public static final int TYPE_MAGNETIC_FIELD = 2; // 0x2
+    field public static final int TYPE_MAGNETIC_FIELD_UNCALIBRATED = 14; // 0xe
     field public static final deprecated int TYPE_ORIENTATION = 3; // 0x3
     field public static final int TYPE_PRESSURE = 6; // 0x6
     field public static final int TYPE_PROXIMITY = 8; // 0x8
     field public static final int TYPE_RELATIVE_HUMIDITY = 12; // 0xc
     field public static final int TYPE_ROTATION_VECTOR = 11; // 0xb
+    field public static final int TYPE_SIGNIFICANT_MOTION = 17; // 0x11
     field public static final deprecated int TYPE_TEMPERATURE = 7; // 0x7
   }
 
@@ -10077,6 +10081,7 @@
   }
 
   public abstract class SensorManager {
+    method public boolean cancelTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor);
     method public static float getAltitude(float, float);
     method public static void getAngleChange(float[], float[], float[]);
     method public android.hardware.Sensor getDefaultSensor(int);
@@ -10092,6 +10097,7 @@
     method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int);
     method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int, android.os.Handler);
     method public static boolean remapCoordinateSystem(float[], int, int, float[]);
+    method public boolean requestTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor);
     method public deprecated void unregisterListener(android.hardware.SensorListener);
     method public deprecated void unregisterListener(android.hardware.SensorListener, int);
     method public void unregisterListener(android.hardware.SensorEventListener, android.hardware.Sensor);
@@ -10155,6 +10161,17 @@
     field public static final float STANDARD_GRAVITY = 9.80665f;
   }
 
+  public final class TriggerEvent {
+    field public android.hardware.Sensor sensor;
+    field public long timestamp;
+    field public final float[] values;
+  }
+
+  public abstract class TriggerEventListener {
+    ctor public TriggerEventListener();
+    method public abstract void onTrigger(android.hardware.TriggerEvent);
+  }
+
 }
 
 package android.hardware.display {
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index 41384d2..af4c074 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -114,11 +114,90 @@
     /** A constant describing an ambient temperature sensor type */
     public static final int TYPE_AMBIENT_TEMPERATURE = 13;
 
-    /** 
+    /**
+     * A constant describing a magnetic field uncalibrated sensor type. See
+     * {@link android.hardware.SensorEvent#values SensorEvent.values} for more
+     * details.
+     * <p>
+     * No periodic calibration is performed (ie: there are no discontinuities
+     * in the data stream while using this sensor). Assumptions that the
+     * magnetic field is due to the Earth's poles is avoided. Factory calibration
+     * and temperature compensation is still performed.
+     * </p>
+     */
+    public static final int TYPE_MAGNETIC_FIELD_UNCALIBRATED = 14;
+
+    /**
+     * Identical to {@link #TYPE_ROTATION_VECTOR} except that it doesn't
+     * use the geomagnetic field. Therefore the Y axis doesn't
+     * point north, but instead to some other reference, that reference is
+     * allowed to drift by the same order of magnitude as the gyroscope
+     * drift around the Z axis.
+     * <p>
+     * In the ideal case, a phone rotated and returning to the same real-world
+     * orientation should report the same game rotation vector
+     * (without using the earth's geomagnetic field). However, the orientation
+     * may drift somewhat over time.
+     * </p>
+     */
+
+    public static final int TYPE_GAME_ROTATION_VECTOR = 15;
+
+    /**
+     * A constant describing a gyroscope uncalibrated sensor type. See
+     * {@link android.hardware.SensorEvent#values SensorEvent.values} for more
+     * details.
+     * <p>
+     * No gyro-drift compensation is performed.
+     * Factory calibration and temperature compensation is still applied
+     * to the rate of rotation (angular speeds).
+     * </p>
+     */
+    public static final int TYPE_GYROSCOPE_UNCALIBRATED = 16;
+
+    /**
+     * A constant describing the significant motion trigger sensor.
+     * See {@link android.hardware.SensorEvent#values} for more details.
+     * <p>
+     * It triggers when an event occurs and then automatically disables
+     * itself. The sensor continues to operate while the device is asleep
+     * and will automatically wake the device to notify when significant
+     * motion is detected. The application does not need to hold any wake
+     * locks for this sensor to trigger.
+     * </p>
+     */
+    public static final int TYPE_SIGNIFICANT_MOTION = 17;
+
+    /**
      * A constant describing all sensor types.
      */
     public static final int TYPE_ALL = -1;
 
+    /* Reporting mode constants for sensors. Each sensor will have exactly one
+       reporting mode associated with it. */
+    // Events are reported at a constant rate.
+    static int REPORTING_MODE_CONTINUOUS = 1;
+
+    // Events are reported only when the value changes.
+    static int REPORTING_MODE_ON_CHANGE = 2;
+
+    // Upon detection of an event, the sensor deactivates itself and then sends a single event.
+    static int REPORTING_MODE_ONE_SHOT = 3;
+
+    // Note: This needs to be updated, whenever a new sensor is added.
+    private static int[] sSensorReportingModes = {
+            REPORTING_MODE_CONTINUOUS, REPORTING_MODE_CONTINUOUS, REPORTING_MODE_CONTINUOUS,
+            REPORTING_MODE_CONTINUOUS, REPORTING_MODE_ON_CHANGE, REPORTING_MODE_CONTINUOUS,
+            REPORTING_MODE_ON_CHANGE, REPORTING_MODE_ON_CHANGE, REPORTING_MODE_CONTINUOUS,
+            REPORTING_MODE_CONTINUOUS, REPORTING_MODE_CONTINUOUS, REPORTING_MODE_ON_CHANGE,
+            REPORTING_MODE_ON_CHANGE, REPORTING_MODE_CONTINUOUS, REPORTING_MODE_CONTINUOUS,
+            REPORTING_MODE_CONTINUOUS, REPORTING_MODE_ONE_SHOT };
+
+    static int getReportingMode(Sensor sensor) {
+        // mType starts from offset 1.
+        return sSensorReportingModes[sensor.mType - 1];
+    }
+
     /* Some of these fields are set only by the native bindings in
      * SensorManager.
      */
@@ -132,7 +211,6 @@
     private float   mPower;
     private int     mMinDelay;
 
-
     Sensor() {
     }
 
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 51a17c1..84c9131 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -17,11 +17,9 @@
 package android.hardware;
 
 /**
- * <p>
  * This class represents a {@link android.hardware.Sensor Sensor} event and
  * holds informations such as the sensor's type, the time-stamp, accuracy and of
  * course the sensor's {@link SensorEvent#values data}.
- * </p>
  *
  * <p>
  * <u>Definition of the coordinate system used by the SensorEvent API.</u>
@@ -67,15 +65,9 @@
      * Sensor.TYPE_ACCELEROMETER}:</h4> All values are in SI units (m/s^2)
      * 
      * <ul>
-     * <p>
-     * values[0]: Acceleration minus Gx on the x-axis
-     * </p>
-     * <p>
-     * values[1]: Acceleration minus Gy on the y-axis
-     * </p>
-     * <p>
-     * values[2]: Acceleration minus Gz on the z-axis
-     * </p>
+     * <li> values[0]: Acceleration minus Gx on the x-axis </li>
+     * <li> values[1]: Acceleration minus Gy on the y-axis </li>
+     * <li> values[2]: Acceleration minus Gz on the z-axis </li>
      * </ul>
      * 
      * <p>
@@ -165,15 +157,9 @@
      * definition of positive rotation and does not agree with the definition of
      * roll given earlier.
      * <ul>
-     * <p>
-     * values[0]: Angular speed around the x-axis
-     * </p>
-     * <p>
-     * values[1]: Angular speed around the y-axis
-     * </p>
-     * <p>
-     * values[2]: Angular speed around the z-axis
-     * </p>
+     * <li> values[0]: Angular speed around the x-axis </li>
+     * <li> values[1]: Angular speed around the y-axis </li>
+     * <li> values[2]: Angular speed around the z-axis </li>
      * </ul>
      * <p>
      * Typically the output of the gyroscope is integrated over time to
@@ -233,22 +219,19 @@
      * </p>
      * <h4>{@link android.hardware.Sensor#TYPE_LIGHT Sensor.TYPE_LIGHT}:</h4>
      * <ul>
-     * <p>
-     * values[0]: Ambient light level in SI lux units
+     * <li>values[0]: Ambient light level in SI lux units </li>
      * </ul>
      * 
      * <h4>{@link android.hardware.Sensor#TYPE_PRESSURE Sensor.TYPE_PRESSURE}:</h4>
      * <ul>
-     * <p>
-     * values[0]: Atmospheric pressure in hPa (millibar)
+     * <li>values[0]: Atmospheric pressure in hPa (millibar) </li>
      * </ul>
      *
      * <h4>{@link android.hardware.Sensor#TYPE_PROXIMITY Sensor.TYPE_PROXIMITY}:
      * </h4>
      * 
      * <ul>
-     * <p>
-     * values[0]: Proximity sensor distance measured in centimeters
+     * <li>values[0]: Proximity sensor distance measured in centimeters </li>
      * </ul>
      * 
      * <p>
@@ -304,39 +287,23 @@
      * </p>
      *
      * <ul>
-     * <p>
-     * values[0]: x*sin(&#952/2)
-     * </p>
-     * <p>
-     * values[1]: y*sin(&#952/2)
-     * </p>
-     * <p>
-     * values[2]: z*sin(&#952/2)
-     * </p>
-     * <p>
-     * values[3]: cos(&#952/2) <i>(optional: only if value.length = 4)</i>
-     * </p>
+     * <li> values[0]: x*sin(&#952/2) </li>
+     * <li> values[1]: y*sin(&#952/2) </li>
+     * <li> values[2]: z*sin(&#952/2) </li>
+     * <li> values[3]: cos(&#952/2) <i>(optional: only if value.length = 4)</i> </li>
      * </ul>
      *
      * <h4>{@link android.hardware.Sensor#TYPE_ORIENTATION
      * Sensor.TYPE_ORIENTATION}:</h4> All values are angles in degrees.
      * 
      * <ul>
-     * <p>
-     * values[0]: Azimuth, angle between the magnetic north direction and the
+     * <li> values[0]: Azimuth, angle between the magnetic north direction and the
      * y-axis, around the z-axis (0 to 359). 0=North, 90=East, 180=South,
-     * 270=West
-     * </p>
-     * 
-     * <p>
-     * values[1]: Pitch, rotation around x-axis (-180 to 180), with positive
-     * values when the z-axis moves <b>toward</b> the y-axis.
-     * </p>
-     * 
-     * <p>
-     * values[2]: Roll, rotation around y-axis (-90 to 90), with positive values
-     * when the x-axis moves <b>toward</b> the z-axis.
-     * </p>
+     * 270=West </li>
+     * <li> values[1]: Pitch, rotation around x-axis (-180 to 180), with positive
+     * values when the z-axis moves <b>toward</b> the y-axis. </li>
+     * <li> values[2]: Roll, rotation around y-axis (-90 to 90), with positive values
+     * when the x-axis moves <b>toward</b> the z-axis. </li>
      * </ul>
      * 
      * <p>
@@ -364,9 +331,7 @@
      * <h4>{@link android.hardware.Sensor#TYPE_RELATIVE_HUMIDITY
      * Sensor.TYPE_RELATIVE_HUMIDITY}:</h4>
      * <ul>
-     * <p>
-     * values[0]: Relative ambient air humidity in percent
-     * </p>
+     * <li> values[0]: Relative ambient air humidity in percent </li>
      * </ul>
      * <p>
      * When relative ambient air humidity and ambient temperature are
@@ -423,21 +388,58 @@
      * </h4>
      *
      * <ul>
-     * <p>
-     * values[0]: ambient (room) temperature in degree Celsius.
+     * <li> values[0]: ambient (room) temperature in degree Celsius.</li>
      * </ul>
      *
      * @see SensorEvent
      * @see GeomagneticField
+     *
+     * <h4>{@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD_UNCALIBRATED} </h4>
+     * All values are in micro-Tesla (uT) and measure the ambient magnetic field
+     * in the X, Y and Z axis.
+     * <p>
+     * No periodic calibration is performed (ie: there are no discontinuities
+     * in the data stream while using this sensor). Assumptions that the the
+     * magnetic field is due to the Earth's poles is avoided. Factory calibration
+     * and temperature compensation is still performed.
+     * </p>
+     *
+     * <h4> {@link android.hardware.Sensor#TYPE_GYROSCOPE_UNCALIBRATED} </h4>
+     * All values are in radians/second and measure the rate of rotation
+     * around the X, Y and Z axis. An estimation of the drift on each axis is
+     * reported as well.
+     * <p>
+     * No gyro-drift compensation is performed. Factory calibration and temperature
+     * compensation is still applied to the rate of rotation (angular speeds).
+     * </p>
+     * <p>
+     * The coordinate system is the same as is used for the
+     * {@link android.hardware.Sensor#TYPE_ACCELEROMETER}
+     * Rotation is positive in the counter-clockwise direction (right-hand rule).
+     * That is, an observer looking from some positive location on the x, y or z axis
+     * at a device positioned on the origin would report positive rotation if the device
+     * appeared to be rotating counter clockwise.
+     * The range would at least be 17.45 rad/s (ie: ~1000 deg/s).
+     * <ul>
+     * <li> values[0] : angular speed (w/o drift compensation) around the X axis in rad/s </li>
+     * <li> values[1] : angular speed (w/o drift compensation) around the Y axis in rad/s </li>
+     * <li> values[2] : angular speed (w/o drift compensation) around the Z axis in rad/s </li>
+     * <li> values[3] : estimated drift around X axis in rad/s </li>
+     * <li> values[4] : estimated drift around Y axis in rad/s </li>
+     * <li> values[5] : estimated drift around Z axis in rad/s </li>
+     * </ul>
+     * </p>
+     * <h4></h4>
+     * <h4> Pro Tip: Always use the length of the values array while performing operations
+     * on it. In earlier versions, this used to be always 3 which has changed now. </h4>
      */
-
     public final float[] values;
 
     /**
      * The sensor that generated this event. See
      * {@link android.hardware.SensorManager SensorManager} for details.
      */
-   public Sensor sensor;
+    public Sensor sensor;
 
     /**
      * The accuracy of this event. See {@link android.hardware.SensorManager
@@ -445,13 +447,11 @@
      */
     public int accuracy;
 
-
     /**
      * The time in nanosecond at which the event happened
      */
     public long timestamp;
 
-
     SensorEvent(int size) {
         values = new float[size];
     }
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index c0d2fae..ce7bc7e 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -38,7 +38,11 @@
  * hours. Note that the system will <i>not</i> disable sensors automatically when
  * the screen turns off.
  * </p>
- *
+ * <p class="note">
+ * Note: Don't use this mechanism with a Trigger Sensor, have a look
+ * at {@link TriggerEventListener}. {@link Sensor#TYPE_SIGNIFICANT_MOTION}
+ * is an example of a trigger sensor.
+ * </p>
  * <pre class="prettyprint">
  * public class SensorActivity extends Activity, implements SensorEventListener {
  *     private final SensorManager mSensorManager;
@@ -515,6 +519,12 @@
     /**
      * Unregisters a listener for the sensors with which it is registered.
      *
+     * <p class="note"></p>
+     * Note: Don't use this method with a one shot trigger sensor such as
+     * {@link Sensor#TYPE_SIGNIFICANT_MOTION}.
+     * Use {@link #cancelTriggerSensor(TriggerEventListener, Sensor)} instead.
+     * </p>
+     *
      * @param listener
      *        a SensorEventListener object
      *
@@ -524,6 +534,7 @@
      * @see #unregisterListener(SensorEventListener)
      * @see #registerListener(SensorEventListener, Sensor, int)
      *
+     * @throws IllegalArgumentException when sensor is a trigger sensor.
      */
     public void unregisterListener(SensorEventListener listener, Sensor sensor) {
         if (listener == null || sensor == null) {
@@ -558,6 +569,12 @@
      * Registers a {@link android.hardware.SensorEventListener
      * SensorEventListener} for the given sensor.
      *
+     * <p class="note"></p>
+     * Note: Don't use this method with a one shot trigger sensor such as
+     * {@link Sensor#TYPE_SIGNIFICANT_MOTION}.
+     * Use {@link #requestTriggerSensor(TriggerEventListener, Sensor)} instead.
+     * </p>
+     *
      * @param listener
      *        A {@link android.hardware.SensorEventListener SensorEventListener}
      *        object.
@@ -581,6 +598,7 @@
      * @see #unregisterListener(SensorEventListener)
      * @see #unregisterListener(SensorEventListener, Sensor)
      *
+     * @throws IllegalArgumentException when sensor is null or a trigger sensor
      */
     public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate) {
         return registerListener(listener, sensor, rate, null);
@@ -590,6 +608,12 @@
      * Registers a {@link android.hardware.SensorEventListener
      * SensorEventListener} for the given sensor.
      *
+     * <p class="note"></p>
+     * Note: Don't use this method with a one shot trigger sensor such as
+     * {@link Sensor#TYPE_SIGNIFICANT_MOTION}.
+     * Use {@link #requestTriggerSensor(TriggerEventListener, Sensor)} instead.
+     * </p>
+     *
      * @param listener
      *        A {@link android.hardware.SensorEventListener SensorEventListener}
      *        object.
@@ -617,6 +641,7 @@
      * @see #unregisterListener(SensorEventListener)
      * @see #unregisterListener(SensorEventListener, Sensor)
      *
+     * @throws IllegalArgumentException when sensor is null or a trigger sensor
      */
     public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate,
             Handler handler) {
@@ -1304,6 +1329,68 @@
         Q[3] = rv[2];
     }
 
+    /**
+     * Requests receiving trigger events for a trigger sensor.
+     *
+     * <p>
+     * When the sensor detects a trigger event condition, such as significant motion in
+     * the case of the {@link Sensor#TYPE_SIGNIFICANT_MOTION}, the provided trigger listener
+     * will be invoked once and then its request to receive trigger events will be canceled.
+     * To continue receiving trigger events, the application must request to receive trigger
+     * events again.
+     * </p>
+     *
+     * @param listener The listener on which the
+     *        {@link TriggerEventListener#onTrigger(TriggerEvent)} will be delivered.
+     * @param sensor The sensor to be enabled.
+     *
+     * @return true if the sensor was successfully enabled.
+     *
+     * @throws IllegalArgumentException when sensor is null or not a trigger sensor.
+     */
+    public boolean requestTriggerSensor(TriggerEventListener listener, Sensor sensor) {
+        return requestTriggerSensorImpl(listener, sensor);
+    }
+
+    /**
+     * @hide
+     */
+    protected abstract boolean requestTriggerSensorImpl(TriggerEventListener listener,
+            Sensor sensor);
+
+    /**
+     * Cancels receiving trigger events for a trigger sensor.
+     *
+     * <p>
+     * Note that a Trigger sensor will be auto disabled if
+     * {@link TriggerEventListener#onTrigger(TriggerEvent)} has triggered.
+     * This method is provided in case the user wants to explicitly cancel the request
+     * to receive trigger events.
+     * </p>
+     *
+     * @param listener The listener on which the
+     *        {@link TriggerEventListener#onTrigger(TriggerEvent)}
+     *        is delivered.It should be the same as the one used
+     *        in {@link #requestTriggerSensor(TriggerEventListener, Sensor)}
+     * @param sensor The sensor for which the trigger request should be canceled.
+     *        If null, it cancels receiving trigger for all sensors associated
+     *        with the listener.
+     *
+     * @return true if successfully canceled.
+     *
+     * @throws IllegalArgumentException when sensor is a trigger sensor.
+     */
+    public boolean cancelTriggerSensor(TriggerEventListener listener, Sensor sensor) {
+        return cancelTriggerSensorImpl(listener, sensor);
+    }
+
+    /**
+     * @hide
+     */
+    protected abstract boolean cancelTriggerSensorImpl(TriggerEventListener listener,
+            Sensor sensor);
+
+
     private LegacySensorManager getLegacySensorManager() {
         synchronized (mSensorListByType) {
             if (mLegacySensorManager == null) {
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 9591631..3c231e5 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -16,18 +16,19 @@
 
 package android.hardware;
 
-import java.util.ArrayList;
-import java.util.Hashtable;
-import java.util.List;
-
-import dalvik.system.CloseGuard;
-
 import android.os.Handler;
 import android.os.Looper;
 import android.os.MessageQueue;
+import android.util.Log;
+import android.util.Pools;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
+import dalvik.system.CloseGuard;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
 
 /**
  * Sensor manager implementation that communicates with the built-in
@@ -45,22 +46,21 @@
     private static final SparseArray<Sensor> sHandleToSensor = new SparseArray<Sensor>();
 
     // Listener list
-    private final ArrayList<SensorEventListenerSensorPair> mListenerDelegates = new ArrayList<SensorEventListenerSensorPair>();
+    private final HashMap<SensorEventListener, SensorEventQueue> mSensorListeners =
+            new HashMap<SensorEventListener, SensorEventQueue>();
+    private final HashMap<TriggerEventListener, TriggerEventQueue> mTriggerListeners =
+            new HashMap<TriggerEventListener, TriggerEventQueue>();
 
-    // Common pool of sensor events.
-    private static SensorEventPool sPool;
+    private static final int MAX_EVENTS = 16;
+    private static Pools.SynchronizedPool<SensorEvent> sSensorEventPool;
+    private static Pools.SynchronizedPool<TriggerEvent> sTriggerEventPool;
 
     // Looper associated with the context in which this instance was created.
     private final Looper mMainLooper;
 
-    // maps a SensorEventListener to a SensorEventQueue
-    private final Hashtable<SensorEventListener, SensorEventQueue> mSensorEventQueueMap;
-
     /** {@hide} */
     public SystemSensorManager(Looper mainLooper) {
         mMainLooper = mainLooper;
-        mSensorEventQueueMap = new Hashtable<SensorEventListener, SensorEventQueue>();
-
         synchronized(sSensorModuleLock) {
             if (!sSensorModuleInitialized) {
                 sSensorModuleInitialized = true;
@@ -81,7 +81,10 @@
                     }
                 } while (i>0);
 
-                sPool = new SensorEventPool( sFullSensorsList.size()*2 );
+                sSensorEventPool = new Pools.SynchronizedPool<SensorEvent>(
+                        sFullSensorsList.size()*2);
+                sTriggerEventPool = new Pools.SynchronizedPool<TriggerEvent>(
+                        sFullSensorsList.size()*2);
             }
         }
     }
@@ -102,128 +105,133 @@
         // Invariants to preserve:
         // - one Looper per SensorEventListener
         // - one Looper per SensorEventQueue
-        // We map SensorEventListeners to a SensorEventQueue, which holds the looper
+        // We map SensorEventListener to a SensorEventQueue, which holds the looper
+        if (sensor == null) throw new IllegalArgumentException("sensor cannot be null");
 
-        if (sensor == null) throw new NullPointerException("sensor cannot be null");
+        // Trigger Sensors should use the requestTriggerSensor call.
+        if (Sensor.getReportingMode(sensor) == Sensor.REPORTING_MODE_ONE_SHOT) return false;
 
-        boolean result;
-        synchronized (mSensorEventQueueMap) {
-            // check if we already have this SensorEventListener, Sensor pair
-            // registered -- if so, we ignore the register. This is not ideal
-            // but this is what the implementation has always been doing.
-            for (SensorEventListenerSensorPair l : mListenerDelegates) {
-                if (l.isSameListenerSensorPair(listener, sensor)) {
-                    // already added, just return silently.
-                    return true;
-                }
-            }
-
-            // now find the SensorEventQueue associated to this listener
-            SensorEventQueue queue = mSensorEventQueueMap.get(listener);
-            if (queue != null) {
-                result = queue.addSensor(sensor, delay);
-                if (result) {
-                    // create a new ListenerDelegate for this pair
-                    mListenerDelegates.add(new SensorEventListenerSensorPair(listener, sensor));
-                }
-            } else {
+        synchronized (mSensorListeners) {
+            SensorEventQueue queue = mSensorListeners.get(listener);
+            if (queue == null) {
                 Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
-                queue = new SensorEventQueue(listener, looper.getQueue());
-                result = queue.addSensor(sensor, delay);
-                if (result) {
-                    // create a new ListenerDelegate for this pair
-                    mListenerDelegates.add(new SensorEventListenerSensorPair(listener, sensor));
-                    mSensorEventQueueMap.put(listener, queue);
-                } else {
+                queue = new SensorEventQueue(listener, looper);
+                if (!queue.addSensor(sensor, delay)) {
                     queue.dispose();
+                    return false;
                 }
+                mSensorListeners.put(listener, queue);
+                return true;
+            } else {
+                return queue.addSensor(sensor, delay);
             }
         }
-        return result;
     }
 
     /** @hide */
     @Override
     protected void unregisterListenerImpl(SensorEventListener listener, Sensor sensor) {
-        synchronized (mSensorEventQueueMap) {
+        // Trigger Sensors should use the cancelTriggerSensor call.
+        if (sensor != null && Sensor.getReportingMode(sensor) == Sensor.REPORTING_MODE_ONE_SHOT) {
+            return;
+        }
 
-            // remove this listener/sensor from our list
-            final ArrayList<SensorEventListenerSensorPair> copy =
-                    new ArrayList<SensorEventListenerSensorPair>(mListenerDelegates);
-            int lastIndex = copy.size()-1;
-            for (int i=lastIndex ; i>= 0 ; i--) {
-                if (copy.get(i).isSameListenerSensorPair(listener, sensor)) {
-                    mListenerDelegates.remove(i);
-                }
-            }
-
-            // find the SensorEventQueue associated to this SensorEventListener
-            SensorEventQueue queue = mSensorEventQueueMap.get(listener);
+        synchronized (mSensorListeners) {
+            SensorEventQueue queue = mSensorListeners.get(listener);
             if (queue != null) {
-                if (sensor != null) {
-                    queue.removeSensor(sensor);
+                boolean result;
+                if (sensor == null) {
+                    result = queue.removeAllSensors();
                 } else {
-                    queue.removeAllSensors();
+                    result = queue.removeSensor(sensor);
                 }
-                if (!queue.hasSensors()) {
-                    mSensorEventQueueMap.remove(listener);
+                if (result && !queue.hasSensors()) {
+                    mSensorListeners.remove(listener);
                     queue.dispose();
                 }
             }
         }
     }
 
+    /** @hide */
+    @Override
+    protected boolean requestTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) {
+        if (sensor == null) throw new IllegalArgumentException("sensor cannot be null");
 
-    /*
-     * ListenerDelegate is essentially a SensorEventListener, Sensor pair
-     * and is associated with a single SensorEventQueue.
-     */
-    private static final class SensorEventListenerSensorPair {
-        private final SensorEventListener mSensorEventListener;
-        private final Sensor mSensor;
-        public SensorEventListenerSensorPair(SensorEventListener listener, Sensor sensor) {
-            mSensorEventListener = listener;
-            mSensor = sensor;
-        }
-        public boolean isSameListenerSensorPair(SensorEventListener listener, Sensor sensor) {
-            // if sensor is null, we match only on the listener
-            if (sensor != null) {
-                return (listener == mSensorEventListener) &&
-                        (sensor.getHandle() == mSensor.getHandle());
+        if (Sensor.getReportingMode(sensor) != Sensor.REPORTING_MODE_ONE_SHOT) return false;
+
+        synchronized (mTriggerListeners) {
+            TriggerEventQueue queue = mTriggerListeners.get(listener);
+            if (queue == null) {
+                queue = new TriggerEventQueue(listener, mMainLooper, this);
+                if (!queue.addSensor(sensor, 0)) {
+                    queue.dispose();
+                    return false;
+                }
+                mTriggerListeners.put(listener, queue);
+                return true;
             } else {
-                return (listener == mSensorEventListener);
+                return queue.addSensor(sensor, 0);
             }
         }
     }
 
+    /** @hide */
+    @Override
+    protected boolean cancelTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) {
+        if (sensor != null && Sensor.getReportingMode(sensor) != Sensor.REPORTING_MODE_ONE_SHOT) {
+            return false;
+        }
+        synchronized (mTriggerListeners) {
+            TriggerEventQueue queue = mTriggerListeners.get(listener);
+            if (queue != null) {
+                boolean result;
+                if (sensor == null) {
+                    result = queue.removeAllSensors();
+                } else {
+                    result = queue.removeSensor(sensor);
+                }
+                if (result && !queue.hasSensors()) {
+                    mTriggerListeners.remove(listener);
+                    queue.dispose();
+                }
+                return result;
+            }
+            return false;
+        }
+    }
+
     /*
-     * SensorEventQueue is the communication channel with the sensor service,
-     * there is a one-to-one mapping between SensorEventQueue and
-     * SensorEventListener.
+     * BaseEventQueue is the communication channel with the sensor service,
+     * SensorEventQueue, TriggerEventQueue are subclases and there is one-to-one mapping between
+     * the queues and the listeners.
      */
-    private static final class SensorEventQueue {
-        private static native int nativeInitSensorEventQueue(SensorEventQueue eventQ, MessageQueue msgQ, float[] scratch);
+    private static abstract class BaseEventQueue {
+        private native int nativeInitBaseEventQueue(BaseEventQueue eventQ, MessageQueue msgQ,
+                float[] scratch);
         private static native int nativeEnableSensor(int eventQ, int handle, int us);
         private static native int nativeDisableSensor(int eventQ, int handle);
         private static native void nativeDestroySensorEventQueue(int eventQ);
         private int nSensorEventQueue;
-        private final SensorEventListener mListener;
         private final SparseBooleanArray mActiveSensors = new SparseBooleanArray();
-        private final SparseIntArray mSensorAccuracies = new SparseIntArray();
-        private final SparseBooleanArray mFirstEvent = new SparseBooleanArray();
+        protected final SparseIntArray mSensorAccuracies = new SparseIntArray();
+        protected final SparseBooleanArray mFirstEvent = new SparseBooleanArray();
         private final CloseGuard mCloseGuard = CloseGuard.get();
         private final float[] mScratch = new float[16];
 
-        public SensorEventQueue(SensorEventListener listener, MessageQueue msgQ) {
-            nSensorEventQueue = nativeInitSensorEventQueue(this, msgQ, mScratch);
-            mListener = listener;
+        BaseEventQueue(Looper looper) {
+            nSensorEventQueue = nativeInitBaseEventQueue(this, looper.getQueue(), mScratch);
             mCloseGuard.open("dispose");
         }
+
         public void dispose() {
             dispose(false);
         }
 
         public boolean addSensor(Sensor sensor, int delay) {
+            // Check if already present.
+            if (mActiveSensors.get(sensor.getHandle())) return false;
+
             if (enableSensor(sensor, delay) == 0) {
                 mActiveSensors.put(sensor.getHandle(), true);
                 return true;
@@ -231,7 +239,7 @@
             return false;
         }
 
-        public void removeAllSensors() {
+        public boolean removeAllSensors() {
             for (int i=0 ; i<mActiveSensors.size(); i++) {
                 if (mActiveSensors.valueAt(i) == true) {
                     int handle = mActiveSensors.keyAt(i);
@@ -244,21 +252,24 @@
                     }
                 }
             }
+            return true;
         }
 
-        public void removeSensor(Sensor sensor) {
+        public boolean removeSensor(Sensor sensor) {
             final int handle = sensor.getHandle();
             if (mActiveSensors.get(handle)) {
                 disableSensor(sensor);
                 mActiveSensors.put(sensor.getHandle(), false);
+                return true;
             }
+            return false;
         }
 
         public boolean hasSensors() {
             // no more sensors are set
             return mActiveSensors.indexOfValue(true) >= 0;
         }
-        
+
         @Override
         protected void finalize() throws Throwable {
             try {
@@ -291,17 +302,30 @@
             if (sensor == null) throw new NullPointerException();
             return nativeDisableSensor(nSensorEventQueue, sensor.getHandle());
         }
+        protected abstract void dispatchSensorEvent(int handle, float[] values, int accuracy,
+                long timestamp);
+    }
+
+    static final class SensorEventQueue extends BaseEventQueue {
+        private final SensorEventListener mListener;
+
+        public SensorEventQueue(SensorEventListener listener, Looper looper) {
+            super(looper);
+            mListener = listener;
+        }
 
         // Called from native code.
         @SuppressWarnings("unused")
-        private void dispatchSensorEvent(int handle, float[] values, int inAccuracy, long timestamp) {
-            // this is always called on the same thread.
-            final SensorEvent t = sPool.getFromPool();
+        @Override
+        protected void dispatchSensorEvent(int handle, float[] values, int inAccuracy,
+                long timestamp) {
+            final Sensor sensor = sHandleToSensor.get(handle);
+            SensorEvent t = sSensorEventPool.acquire();
+            if (t == null) t = new SensorEvent(MAX_EVENTS);
             try {
-                final Sensor sensor = sHandleToSensor.get(handle);
-                final SensorEventListener listener = mListener;
-                // FIXME: handle more than 3 values
-                System.arraycopy(values, 0, t.values, 0, 3);
+                // Copy the entire values array.
+                // Any changes in length will be handled at the native layer.
+                System.arraycopy(values, 0, t.values, 0, t.values.length);
                 t.timestamp = timestamp;
                 t.accuracy = inAccuracy;
                 t.sensor = sensor;
@@ -313,72 +337,57 @@
                         final int accuracy = mSensorAccuracies.get(handle);
                         if ((t.accuracy >= 0) && (accuracy != t.accuracy)) {
                             mSensorAccuracies.put(handle, t.accuracy);
-                            listener.onAccuracyChanged(t.sensor, t.accuracy);
+                            mListener.onAccuracyChanged(t.sensor, t.accuracy);
                         }
                         break;
                     default:
                         // For other sensors, just report the accuracy once
                         if (mFirstEvent.get(handle) == false) {
                             mFirstEvent.put(handle, true);
-                            listener.onAccuracyChanged(
+                            mListener.onAccuracyChanged(
                                     t.sensor, SENSOR_STATUS_ACCURACY_HIGH);
                         }
                         break;
                 }
-                listener.onSensorChanged(t);
+                mListener.onSensorChanged(t);
             } finally {
-                sPool.returnToPool(t);
+                sSensorEventPool.release(t);
             }
         }
     }
 
-    /*
-     * A dumb pool of SensorEvent
-     */
-    private static final class SensorEventPool {
-        private final int mPoolSize;
-        private final SensorEvent mPool[];
-        private int mNumItemsInPool;
+    static final class TriggerEventQueue extends BaseEventQueue {
+        private final TriggerEventListener mListener;
+        private SensorManager mManager;
 
-        private SensorEvent createSensorEvent() {
-            // maximal size for all legacy events is 3
-            return new SensorEvent(3);
+        public TriggerEventQueue(TriggerEventListener listener, Looper looper,
+                SensorManager manager) {
+            super(looper);
+            mListener = listener;
+            mManager = manager;
         }
 
-        SensorEventPool(int poolSize) {
-            mPoolSize = poolSize;
-            mNumItemsInPool = poolSize;
-            mPool = new SensorEvent[poolSize];
-        }
+        // Called from native code.
+        @SuppressWarnings("unused")
+        @Override
+        protected void dispatchSensorEvent(int handle, float[] values, int accuracy, long timestamp) {
+            final Sensor sensor = sHandleToSensor.get(handle);
+            TriggerEvent t = sTriggerEventPool.acquire();
+            if (t == null) t = new TriggerEvent(MAX_EVENTS);
 
-        SensorEvent getFromPool() {
-            SensorEvent t = null;
-            synchronized (this) {
-                if (mNumItemsInPool > 0) {
-                    // remove the "top" item from the pool
-                    final int index = mPoolSize - mNumItemsInPool;
-                    t = mPool[index];
-                    mPool[index] = null;
-                    mNumItemsInPool--;
-                }
-            }
-            if (t == null) {
-                // the pool was empty or this item was removed from the pool for
-                // the first time. In any case, we need to create a new item.
-                t = createSensorEvent();
-            }
-            return t;
-        }
+            try {
+                // Copy the entire values array.
+                // Any changes in length will be handled at the native layer.
+                System.arraycopy(values, 0, t.values, 0, t.values.length);
+                t.timestamp = timestamp;
+                t.sensor = sensor;
 
-        void returnToPool(SensorEvent t) {
-            synchronized (this) {
-                // is there space left in the pool?
-                if (mNumItemsInPool < mPoolSize) {
-                    // if so, return the item to the pool
-                    mNumItemsInPool++;
-                    final int index = mPoolSize - mNumItemsInPool;
-                    mPool[index] = t;
-                }
+                // A trigger sensor should be auto disabled.
+                mManager.cancelTriggerSensorImpl(mListener, sensor);
+
+                mListener.onTrigger(t);
+            } finally {
+                sTriggerEventPool.release(t);
             }
         }
     }
diff --git a/core/java/android/hardware/TriggerEvent.java b/core/java/android/hardware/TriggerEvent.java
new file mode 100644
index 0000000..bdd39f3
--- /dev/null
+++ b/core/java/android/hardware/TriggerEvent.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2013 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.hardware;
+
+/**
+ * This class represents a Trigger Event - the event
+ * associated with a Trigger Sensor. When the sensor detects a trigger
+ * event condition, such as significant motion in the case of the
+ * {@link Sensor#TYPE_SIGNIFICANT_MOTION}, the {@link TriggerEventListener}
+ * is called with the TriggerEvent. The sensor is automatically canceled
+ * after the trigger.
+ * <p>
+ * This class holds information such as the value of the sensor
+ * when the trigger happened, the timestamp along with detailed
+ * information regarding the Sensor itself.
+ * </p>
+ * @see android.hardware.SensorManager
+ * @see android.hardware.TriggerEvent
+ * @see android.hardware.Sensor
+ */
+public final class TriggerEvent {
+    /**
+     * <p>
+     * The length and contents of the {@link #values values} array depends on
+     * which {@link android.hardware.Sensor sensor} type is being monitored (see
+     * also {@link SensorEvent} for a definition of the coordinate system used).
+     * </p>
+     * <h4> {@link Sensor#TYPE_SIGNIFICANT_MOTION} </h4>
+     * The value field is of length 1. value[0] = 1.0 when the sensor triggers.
+     * 1.0 is the only allowed value.
+     */
+    public final float[] values;
+
+    /**
+     * The sensor that generated this event. See
+     * {@link android.hardware.SensorManager SensorManager} for details.
+     */
+    public Sensor sensor;
+
+    /**
+     * The time in nanosecond at which the event happened
+     */
+    public long timestamp;
+
+    TriggerEvent(int size) {
+        values = new float[size];
+    }
+}
diff --git a/core/java/android/hardware/TriggerEventListener.java b/core/java/android/hardware/TriggerEventListener.java
new file mode 100644
index 0000000..82b8907
--- /dev/null
+++ b/core/java/android/hardware/TriggerEventListener.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2013 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.hardware;
+
+/**
+ * This class is the listener used to handle Trigger Sensors.
+ * Trigger Sensors are sensors that trigger an event and are automatically
+ * disabled. {@link Sensor#TYPE_SIGNIFICANT_MOTION} is one such example.
+ * <p>
+ * SensorManager lets you access the device's {@link android.hardware.Sensor
+ * sensors}. Get an instance of this class by calling
+ * {@link android.content.Context#getSystemService(java.lang.String)
+ * Context.getSystemService()} with the argument
+ * {@link android.content.Context#SENSOR_SERVICE}.
+ * Usage details are explained in the example below.
+ * </p>
+ *
+ * <pre class="prettyprint">
+ * class TriggerListener extends TriggerEventListener {
+ *     @Override
+ *     public void onTrigger(TriggerEvent event) {
+ *          // Do Work.
+ *
+ *     // As it is a one shot sensor, it will be canceled automatically.
+ *     // SensorManager.requestTriggerSensor(this, mSigMotion); needs to
+ *     // be called again, if needed.
+ *     }
+ * }
+ * public class SensorActivity extends Activity {
+ *     private final SensorManager mSensorManager;
+ *     private final Sensor mSigMotion;
+ *     private final TriggerEventListener mListener = new TriggerEventListener();
+ *
+ *     public SensorActivity() {
+ *         mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
+ *         mSigMotion = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
+ *     }
+ *
+ *     protected void onResume() {
+ *         super.onResume();
+ *         mSensorManager.requestTriggerSensor(mListener, mSigMotion);
+ *     }
+ *
+ *     protected void onPause() {
+ *         super.onPause();
+ *         // Call disable to ensure that the trigger request has been canceled.
+ *         mSensorManager.cancelTriggerSensor(mListener, mSigMotion);
+ *     }
+ *
+ * }
+ * </pre>
+ *
+ * @see TriggerEvent
+ * @see Sensor
+ */
+public abstract class TriggerEventListener {
+    /**
+     * The method that will be called when the sensor
+     * is triggered. Override this method in your implementation
+     * of this class.
+     *
+     * @param event The details of the event.
+     */
+    public abstract void onTrigger(TriggerEvent event);
+}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index ec8d77e..5d13a18 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -146,9 +146,9 @@
         if (route != null) {
             String routeIface = route.getInterface();
             if (routeIface != null && !routeIface.equals(mIfaceName)) {
-                throw new IllegalStateException(
+                throw new IllegalArgumentException(
                    "Route added with non-matching interface: " + routeIface +
-                   " vs. mIfaceName");
+                   " vs. " + mIfaceName);
             }
             mRoutes.add(routeWithInterface(route));
         }
@@ -370,7 +370,7 @@
     public CompareResult<RouteInfo> compareRoutes(LinkProperties target) {
         /*
          * Duplicate the RouteInfos into removed, we will be removing
-         * routes which are common between mDnses and target
+         * routes which are common between mRoutes and target
          * leaving the routes that are different. And route address which
          * are in target but not in mRoutes are placed in added.
          */
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index c0a894b..2a2f7cf 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -23,8 +23,8 @@
 import java.net.Socket;
 import java.net.SocketException;
 import java.security.KeyManagementException;
+import java.security.PrivateKey;
 import java.security.cert.X509Certificate;
-import java.security.interfaces.ECPrivateKey;
 import javax.net.SocketFactory;
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.HttpsURLConnection;
@@ -89,7 +89,7 @@
     private TrustManager[] mTrustManagers = null;
     private KeyManager[] mKeyManagers = null;
     private byte[] mNpnProtocols = null;
-    private ECPrivateKey mChannelIdPrivateKey = null;
+    private PrivateKey mChannelIdPrivateKey = null;
 
     private final int mHandshakeTimeoutMillis;
     private final SSLClientSessionCache mSessionCache;
@@ -321,7 +321,7 @@
     }
 
     /**
-     * Sets the {@link ECPrivateKey} to be used for TLS Channel ID by connections made by this
+     * Sets the private key to be used for TLS Channel ID by connections made by this
      * factory.
      *
      * @param privateKey private key (enables TLS Channel ID) or {@code null} for no key (disables
@@ -330,7 +330,7 @@
      *
      * @hide
      */
-    public void setChannelIdPrivateKey(ECPrivateKey privateKey) {
+    public void setChannelIdPrivateKey(PrivateKey privateKey) {
         mChannelIdPrivateKey = privateKey;
     }
 
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index 67041ac..c7dacf3 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -5419,7 +5419,7 @@
         ClipData clipData = cm.getPrimaryClip();
         if (clipData != null) {
             ClipData.Item clipItem = clipData.getItemAt(0);
-            CharSequence pasteText = clipItem.getText();
+            CharSequence pasteText = clipItem.coerceToText(mContext);
             if (mInputConnection != null) {
                 mInputConnection.replaceSelection(pasteText);
             }
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 3083cb1..6374494 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -31,7 +31,7 @@
 static struct {
     jclass clazz;
     jmethodID dispatchSensorEvent;
-} gSensorEventQueueClassInfo;
+} gBaseEventQueueClassInfo;
 
 namespace android {
 
@@ -145,7 +145,7 @@
                 env->SetFloatArrayRegion(mScratch, 0, 16, buffer[i].data);
 
                 env->CallVoidMethod(mReceiverObject,
-                        gSensorEventQueueClassInfo.dispatchSensorEvent,
+                        gBaseEventQueueClassInfo.dispatchSensorEvent,
                         buffer[i].sensor,
                         mScratch,
                         buffer[i].vector.status,
@@ -209,9 +209,9 @@
             (void*)nativeGetNextSensor },
 };
 
-static JNINativeMethod gSensorEventQueueMethods[] = {
-    {"nativeInitSensorEventQueue",
-            "(Landroid/hardware/SystemSensorManager$SensorEventQueue;Landroid/os/MessageQueue;[F)I",
+static JNINativeMethod gBaseEventQueueMethods[] = {
+    {"nativeInitBaseEventQueue",
+            "(Landroid/hardware/SystemSensorManager$BaseEventQueue;Landroid/os/MessageQueue;[F)I",
             (void*)nativeInitSensorEventQueue },
 
     {"nativeEnableSensor",
@@ -245,13 +245,13 @@
     jniRegisterNativeMethods(env, "android/hardware/SystemSensorManager",
             gSystemSensorManagerMethods, NELEM(gSystemSensorManagerMethods));
 
-    jniRegisterNativeMethods(env, "android/hardware/SystemSensorManager$SensorEventQueue",
-            gSensorEventQueueMethods, NELEM(gSensorEventQueueMethods));
+    jniRegisterNativeMethods(env, "android/hardware/SystemSensorManager$BaseEventQueue",
+            gBaseEventQueueMethods, NELEM(gBaseEventQueueMethods));
 
-    FIND_CLASS(gSensorEventQueueClassInfo.clazz, "android/hardware/SystemSensorManager$SensorEventQueue");
+    FIND_CLASS(gBaseEventQueueClassInfo.clazz, "android/hardware/SystemSensorManager$BaseEventQueue");
 
-    GET_METHOD_ID(gSensorEventQueueClassInfo.dispatchSensorEvent,
-            gSensorEventQueueClassInfo.clazz,
+    GET_METHOD_ID(gBaseEventQueueClassInfo.dispatchSensorEvent,
+            gBaseEventQueueClassInfo.clazz,
             "dispatchSensorEvent", "(I[FIJ)V");
 
     return 0;
diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
index e3b6b5f..fffaa00 100644
--- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java
+++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
@@ -197,4 +197,63 @@
         }
     }
 
+    private void assertAllRoutesHaveInterface(String iface, LinkProperties lp) {
+        for (RouteInfo r : lp.getRoutes()) {
+            assertEquals(iface, r.getInterface());
+        }
+    }
+
+    @SmallTest
+    public void testRouteInterfaces() {
+        LinkAddress prefix = new LinkAddress(
+            NetworkUtils.numericToInetAddress("2001:db8::"), 32);
+        InetAddress address = NetworkUtils.numericToInetAddress(ADDRV6);
+
+        // Add a route with no interface to a LinkProperties with no interface. No errors.
+        LinkProperties lp = new LinkProperties();
+        RouteInfo r = new RouteInfo(prefix, address, null);
+        lp.addRoute(r);
+        assertEquals(1, lp.getRoutes().size());
+        assertAllRoutesHaveInterface(null, lp);
+
+        // Add a route with an interface. Except an exception.
+        r = new RouteInfo(prefix, address, "wlan0");
+        try {
+          lp.addRoute(r);
+          fail("Adding wlan0 route to LP with no interface, expect exception");
+        } catch (IllegalArgumentException expected) {}
+
+        // Change the interface name. All the routes should change their interface name too.
+        lp.setInterfaceName("rmnet0");
+        assertAllRoutesHaveInterface("rmnet0", lp);
+
+        // Now add a route with the wrong interface. This causes an exception too.
+        try {
+          lp.addRoute(r);
+          fail("Adding wlan0 route to rmnet0 LP, expect exception");
+        } catch (IllegalArgumentException expected) {}
+
+        // If the interface name matches, the route is added.
+        lp.setInterfaceName("wlan0");
+        lp.addRoute(r);
+        assertEquals(2, lp.getRoutes().size());
+        assertAllRoutesHaveInterface("wlan0", lp);
+
+        // Routes with null interfaces are converted to wlan0.
+        r = RouteInfo.makeHostRoute(NetworkUtils.numericToInetAddress(ADDRV6), null);
+        lp.addRoute(r);
+        assertEquals(3, lp.getRoutes().size());
+        assertAllRoutesHaveInterface("wlan0", lp);
+
+        // Check comparisons work.
+        LinkProperties lp2 = new LinkProperties(lp);
+        assertAllRoutesHaveInterface("wlan0", lp);
+        assertEquals(0, lp.compareRoutes(lp2).added.size());
+        assertEquals(0, lp.compareRoutes(lp2).removed.size());
+
+        lp2.setInterfaceName("p2p0");
+        assertAllRoutesHaveInterface("p2p0", lp2);
+        assertEquals(3, lp.compareRoutes(lp2).added.size());
+        assertEquals(3, lp.compareRoutes(lp2).removed.size());
+    }
 }
diff --git a/docs/downloads/training/InteractiveChart.zip b/docs/downloads/training/InteractiveChart.zip
new file mode 100644
index 0000000..95248ad
--- /dev/null
+++ b/docs/downloads/training/InteractiveChart.zip
Binary files differ
diff --git a/docs/html/training/gestures/detector.jd b/docs/html/training/gestures/detector.jd
index 06d0e98..65ddb1b 100644
--- a/docs/html/training/gestures/detector.jd
+++ b/docs/html/training/gestures/detector.jd
@@ -25,12 +25,18 @@
    <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
 
 </div>
 </div>
diff --git a/docs/html/training/gestures/index.jd b/docs/html/training/gestures/index.jd
index 0191450..16ca7b0 100644
--- a/docs/html/training/gestures/index.jd
+++ b/docs/html/training/gestures/index.jd
@@ -20,12 +20,18 @@
     <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
 
 </div>
 </div>
diff --git a/docs/html/training/gestures/movement.jd b/docs/html/training/gestures/movement.jd
index f2c49d7..fdc1ea4 100644
--- a/docs/html/training/gestures/movement.jd
+++ b/docs/html/training/gestures/movement.jd
@@ -24,12 +24,18 @@
     <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
 
 </div>
 </div>
diff --git a/docs/html/training/gestures/multi.jd b/docs/html/training/gestures/multi.jd
index d4c5b1d..6a0df11 100644
--- a/docs/html/training/gestures/multi.jd
+++ b/docs/html/training/gestures/multi.jd
@@ -25,12 +25,18 @@
    <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
 
 </div>
 </div>
diff --git a/docs/html/training/gestures/scale.jd b/docs/html/training/gestures/scale.jd
index 17e4085..f2e4eb8 100644
--- a/docs/html/training/gestures/scale.jd
+++ b/docs/html/training/gestures/scale.jd
@@ -15,6 +15,7 @@
 <h2>This lesson teaches you to</h2>
 <ol>
   <li><a href="#drag">Drag an Object</a></li>
+  <li><a href="#pan">Drag to Pan</a></li>
   <li><a href="#scale">Use Touch to Perform Scaling</a></li>
 </ol>
 
@@ -25,20 +26,25 @@
     <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
 
 </div>
 </div>
+
 <p>This lesson describes how to use touch gestures to drag and scale on-screen
 objects, using {@link android.view.View#onTouchEvent onTouchEvent()} to intercept
-touch events. Here is the original <a
-href="http://code.google.com/p/android-touchexample/">source code</a>
-for the examples used in this lesson.
+touch events. 
 </p>
 
 <h2 id="drag">Drag an Object</h2>
@@ -128,17 +134,15 @@
         final float x = MotionEventCompat.getX(ev, pointerIndex);
         final float y = MotionEventCompat.getY(ev, pointerIndex);
             
-        // Only move if the ScaleGestureDetector isn't processing a gesture.
-        if (!mScaleDetector.isInProgress()) {
-            // Calculate the distance moved
-            final float dx = x - mLastTouchX;
-            final float dy = y - mLastTouchY;
+        // Calculate the distance moved
+        final float dx = x - mLastTouchX;
+        final float dy = y - mLastTouchY;
 
-            mPosX += dx;
-            mPosY += dy;
+        mPosX += dx;
+        mPosY += dy;
 
-            invalidate();
-        }
+        invalidate();
+
         // Remember this touch position for the next move event
         mLastTouchX = x;
         mLastTouchY = y;
@@ -175,6 +179,88 @@
     return true;
 }</pre>
 
+<h2 id="pan">Drag to Pan</h2>
+
+<p>The previous section showed an example of dragging an object around the screen. Another 
+common scenario is <em>panning</em>, which is when a user's dragging motion causes scrolling 
+in both the x and y axes. The above snippet directly intercepted the {@link android.view.MotionEvent} 
+actions to implement dragging. The snippet in this section takes advantage of the platform's 
+built-in support for common gestures. It overrides 
+{@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} in 
+{@link android.view.GestureDetector.SimpleOnGestureListener}.</p>
+
+<p>To provide a little more context, {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} 
+is called when a user is dragging his finger to pan the content. 
+{@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} is only called when 
+a finger is down; as soon as the finger is lifted from the screen, the gesture either ends, 
+or a fling gesture is started (if the finger was moving with some speed just before it was lifted). 
+For more discussion of scrolling vs. flinging, see <a href="scroll.html">Animating a Scroll Gesture</a>.</p>
+
+<p>Here is the snippet for {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()}:
+
+
+<pre>// The current viewport. This rectangle represents the currently visible 
+// chart domain and range. 
+private RectF mCurrentViewport = 
+        new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);
+
+// The current destination rectangle (in pixel coordinates) into which the 
+// chart data should be drawn.
+private Rect mContentRect;
+
+private final GestureDetector.SimpleOnGestureListener mGestureListener
+            = new GestureDetector.SimpleOnGestureListener() {
+...
+
+&#64;Override
+public boolean onScroll(MotionEvent e1, MotionEvent e2, 
+            float distanceX, float distanceY) {
+    // Scrolling uses math based on the viewport (as opposed to math using pixels).
+    
+    // Pixel offset is the offset in screen pixels, while viewport offset is the
+    // offset within the current viewport. 
+    float viewportOffsetX = distanceX * mCurrentViewport.width() 
+            / mContentRect.width();
+    float viewportOffsetY = -distanceY * mCurrentViewport.height() 
+            / mContentRect.height();
+    ...
+    // Updates the viewport, refreshes the display. 
+    setViewportBottomLeft(
+            mCurrentViewport.left + viewportOffsetX,
+            mCurrentViewport.bottom + viewportOffsetY);
+    ...
+    return true;
+}</pre>
+
+<p>The implementation of {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} 
+scrolls the viewport in response to the touch gesture:</p>
+
+<pre>
+/**
+ * Sets the current viewport (defined by mCurrentViewport) to the given
+ * X and Y positions. Note that the Y value represents the topmost pixel position, 
+ * and thus the bottom of the mCurrentViewport rectangle.
+ */
+private void setViewportBottomLeft(float x, float y) {
+    /*
+     * Constrains within the scroll range. The scroll range is simply the viewport 
+     * extremes (AXIS_X_MAX, etc.) minus the viewport size. For example, if the 
+     * extremes were 0 and 10, and the viewport size was 2, the scroll range would 
+     * be 0 to 8.
+     */
+
+    float curWidth = mCurrentViewport.width();
+    float curHeight = mCurrentViewport.height();
+    x = Math.max(AXIS_X_MIN, Math.min(x, AXIS_X_MAX - curWidth));
+    y = Math.max(AXIS_Y_MIN + curHeight, Math.min(y, AXIS_Y_MAX));
+
+    mCurrentViewport.set(x, y - curHeight, x + curWidth, y);
+
+    // Invalidates the View to update the display.
+    ViewCompat.postInvalidateOnAnimation(this);
+}
+</pre>
+
 <h2 id="scale">Use Touch to Perform Scaling</h2>
 
 <p>As discussed in <a href="detector.html">Detecting Common Gestures</a>,
@@ -191,10 +277,10 @@
 {@link android.view.ScaleGestureDetector.SimpleOnScaleGestureListener} 
 as a helper class that you can extend if you don’t care about all of the reported events.</p>
 
-<p>Here is a snippet that gives you the basic idea of how to perform scaling.
-Here is the original <a
-href="http://code.google.com/p/android-touchexample/">source code</a>
-for the examples.</p>
+
+<h3>Basic scaling example</h3>
+
+<p>Here is a snippet that illustrates the basic ingredients involved in scaling.</p>
 
 <pre>private ScaleGestureDetector mScaleDetector;
 private float mScaleFactor = 1.f;
@@ -238,3 +324,88 @@
         return true;
     }
 }</pre>
+
+
+
+
+<h3>More complex scaling example</h3>
+<p>Here is a more complex example from the {@code InteractiveChart} sample provided with this class. 
+The {@code InteractiveChart} sample supports both scrolling (panning) and scaling with multiple fingers,
+using the {@link android.view.ScaleGestureDetector} "span" 
+({@link android.view.ScaleGestureDetector#getCurrentSpanX getCurrentSpanX/Y}) and 
+"focus" ({@link android.view.ScaleGestureDetector#getFocusX getFocusX/Y}) features:</p>
+
+<pre>&#64;Override
+private RectF mCurrentViewport = 
+        new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);
+private Rect mContentRect;
+private ScaleGestureDetector mScaleGestureDetector;
+...
+public boolean onTouchEvent(MotionEvent event) {
+    boolean retVal = mScaleGestureDetector.onTouchEvent(event);
+    retVal = mGestureDetector.onTouchEvent(event) || retVal;
+    return retVal || super.onTouchEvent(event);
+}
+
+/**
+ * The scale listener, used for handling multi-finger scale gestures.
+ */
+private final ScaleGestureDetector.OnScaleGestureListener mScaleGestureListener
+        = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
+    /**
+     * This is the active focal point in terms of the viewport. Could be a local
+     * variable but kept here to minimize per-frame allocations.
+     */
+    private PointF viewportFocus = new PointF();
+    private float lastSpanX;
+    private float lastSpanY;
+
+    // Detects that new pointers are going down.
+    &#64;Override
+    public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
+        lastSpanX = ScaleGestureDetectorCompat.
+                getCurrentSpanX(scaleGestureDetector);
+        lastSpanY = ScaleGestureDetectorCompat.
+                getCurrentSpanY(scaleGestureDetector);
+        return true;
+    }
+
+    &#64;Override
+    public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
+
+        float spanX = ScaleGestureDetectorCompat.
+                getCurrentSpanX(scaleGestureDetector);
+        float spanY = ScaleGestureDetectorCompat.
+                getCurrentSpanY(scaleGestureDetector);
+
+        float newWidth = lastSpanX / spanX * mCurrentViewport.width();
+        float newHeight = lastSpanY / spanY * mCurrentViewport.height();
+
+        float focusX = scaleGestureDetector.getFocusX();
+        float focusY = scaleGestureDetector.getFocusY();
+        // Makes sure that the chart point is within the chart region.
+        // See the sample for the implementation of hitTest().
+        hitTest(scaleGestureDetector.getFocusX(),
+                scaleGestureDetector.getFocusY(),
+                viewportFocus);
+
+        mCurrentViewport.set(
+                viewportFocus.x
+                        - newWidth * (focusX - mContentRect.left)
+                        / mContentRect.width(),
+                viewportFocus.y
+                        - newHeight * (mContentRect.bottom - focusY)
+                        / mContentRect.height(),
+                0,
+                0);
+        mCurrentViewport.right = mCurrentViewport.left + newWidth;
+        mCurrentViewport.bottom = mCurrentViewport.top + newHeight;     
+        ...
+        // Invalidates the View to update the display.
+        ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this);
+
+        lastSpanX = spanX;
+        lastSpanY = spanY;
+        return true;
+    }
+};</pre>
diff --git a/docs/html/training/gestures/scroll.jd b/docs/html/training/gestures/scroll.jd
index 8576948b..bd1537a 100644
--- a/docs/html/training/gestures/scroll.jd
+++ b/docs/html/training/gestures/scroll.jd
@@ -14,6 +14,7 @@
 <!-- table of contents -->
 <h2>This lesson teaches you to</h2>
 <ol>
+  <li><a href="#term">Understand Scrolling Terminology</a></li>
   <li><a href="#scroll">Implement Touch-Based Scrolling</a></li>
 </ol>
 
@@ -24,12 +25,18 @@
     <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
 
 </div>
 </div>
@@ -45,7 +52,26 @@
 
 <p>You can use scrollers ({@link android.widget.Scroller} or {@link
 android.widget.OverScroller}) to collect the data you need to produce a
-scrolling animation in response to a touch event.</p>
+scrolling animation in response to a touch event. They are similar, but
+{@link android.widget.OverScroller}
+includes methods for indicating to users that they've reached the content edges 
+after a pan or fling gesture. The {@code InteractiveChart} sample 
+uses the the {@link android.widget.EdgeEffect} class 
+(actually the {@link android.support.v4.widget.EdgeEffectCompat} class)
+to display a "glow" effect when users reach the content edges.</p>
+
+<p class="note"><strong>Note:</strong> We recommend that you
+use {@link android.widget.OverScroller} rather than {@link
+android.widget.Scroller} for scrolling animations.
+{@link android.widget.OverScroller} provides the best backward
+compatibility with older devices.
+<br />
+Also note that you generally only need to use scrollers
+when implementing scrolling yourself. {@link android.widget.ScrollView} and
+{@link android.widget.HorizontalScrollView} do all of this for you if you nest your 
+layout within them.
+</p>
+
 
 <p>A scroller is used  to animate scrolling over time, using platform-standard
 scrolling physics (friction, velocity, etc.). The scroller itself doesn't
@@ -54,101 +80,280 @@
 responsibility to get and apply new coordinates at a rate that will make the
 scrolling animation look smooth.</p>
 
-<p class="note"><strong>Note:</strong> You generally only need to use scrollers
-when implementing scrolling yourself. {@link android.widget.ScrollView} and
-{@link android.widget.HorizontalScrollView} do all this for you do all of this for you if you nest your layout within them.</p>
-
-<h2 id = "scroll">Implement Touch-Based Scrolling</h2>
 
 
-<p>This snippet illustrates the basics of using a scroller. It uses a 
-{@link android.view.GestureDetector}, and overrides the  
-{@link android.view.GestureDetector.SimpleOnGestureListener} methods 
-{@link android.view.GestureDetector.OnGestureListener#onDown onDown()} and 
-{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}. It also 
-overrides {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} 
-to return {@code false} since you don't need to animate a scroll.</p>
+<h2 id="term">Understand Scrolling Terminology</h2>
 
+<p>"Scrolling" is a word that can take on different meanings in Android, depending on the context.</p>
 
-<p>It's common to use scrollers in conjunction with a fling gesture, but they
+<p><strong>Scrolling</strong> is the general process of moving the viewport (that is, the 'window' 
+of content you're looking at). When scrolling is in both the x and y axes, it's called 
+<em>panning</em>. The sample application provided with this class, {@code InteractiveChart}, illustrates 
+two different types of scrolling, dragging and flinging:</p>
+<ul>
+    <li><strong>Dragging</strong> is the type of scrolling that occurs when a user drags her 
+finger across the touch screen. Simple dragging is often implemented by overriding 
+{@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} in 
+{@link android.view.GestureDetector.OnGestureListener}. For more discussion of dragging, see 
+<a href="dragging.jd">Dragging and Scaling</a>.</li>
+
+    <li><strong>Flinging</strong> is the type of scrolling that occurs when a user 
+drags and lifts her finger quickly. After the user lifts her finger, you generally 
+want to keep scrolling (moving the viewport), but decelerate until the viewport stops moving. 
+Flinging can be implemented by overriding 
+{@link android.view.GestureDetector.OnGestureListener#onFling onFling()} 
+in {@link android.view.GestureDetector.OnGestureListener}, and by using 
+a scroller object. This is the use 
+case that is the topic of this lesson.</li>
+</ul>
+
+<p>It's common to use scroller objects 
+in conjunction with a fling gesture, but they
 can be used in pretty much any context where you want the UI to display
-scrolling in response to a touch event. For example, you could override {@link
-android.view.View#onTouchEvent onTouchEvent()} to process touch events directly,
-and produce a scrolling effect in response to those touch events.</p>
+scrolling in response to a touch event. For example, you could override  
+{@link android.view.View#onTouchEvent onTouchEvent()} to process touch 
+events directly, and produce a scrolling effect or a "snapping to page" animation 
+in response to those touch events.</p>
 
-<pre>
-private OverScroller mScroller = new OverScroller(context);
 
-private GestureDetector.SimpleOnGestureListener mGestureListener
+<h2 id="#scroll">Implement Touch-Based Scrolling</h2> 
+
+<p>This section describes how to use a scroller.
+The snippet shown below comes from the {@code InteractiveChart} sample 
+provided with this class.
+It uses a 
+{@link android.view.GestureDetector}, and overrides the  
+{@link android.view.GestureDetector.SimpleOnGestureListener} method 
+{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}.
+It uses {@link android.widget.OverScroller} to track the fling gesture.
+If the user reaches the content edges 
+after the fling gesture, the app displays a "glow" effect.
+</p>
+
+<p class="note"><strong>Note:</strong> The {@code InteractiveChart} sample app displays a 
+chart that you can zoom, pan, scroll, and so on. In the following snippet, 
+{@code mContentRect} represents the rectangle coordinates within the view that the chart 
+will be drawn into. At any given time, a subset of the total chart domain and range are drawn 
+into this rectangular area. 
+{@code mCurrentViewport} represents the portion of the chart that is currently 
+visible in the screen. Because pixel offsets are generally treated as integers, 
+{@code mContentRect} is of the type {@link android.graphics.Rect}. Because the 
+graph domain and range are decimal/float values, {@code mCurrentViewport} is of 
+the type {@link android.graphics.RectF}.</p>
+
+<p>The first part of the snippet shows the implementation of 
+{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}:</p>
+
+<pre>// The current viewport. This rectangle represents the currently visible 
+// chart domain and range. The viewport is the part of the app that the
+// user manipulates via touch gestures.
+private RectF mCurrentViewport = 
+        new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);
+
+// The current destination rectangle (in pixel coordinates) into which the 
+// chart data should be drawn.
+private Rect mContentRect;
+
+private OverScroller mScroller;
+private RectF mScrollerStartViewport;
+...
+private final GestureDetector.SimpleOnGestureListener mGestureListener
         = new GestureDetector.SimpleOnGestureListener() {
     &#64;Override
     public boolean onDown(MotionEvent e) {
-        // Abort any active scroll animations and invalidate.
+        // Initiates the decay phase of any active edge effects.
+        releaseEdgeEffects();
+        mScrollerStartViewport.set(mCurrentViewport);
+        // Aborts any active scroll animations and invalidates.
         mScroller.forceFinished(true);
-        // There is also a compatibility version: 
-        // ViewCompat.postInvalidateOnAnimation
-        postInvalidateOnAnimation();
+        ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this);
         return true;
     }
-
-    &#64;Override
-    public boolean onScroll(MotionEvent e1, MotionEvent e2, 
-            float distanceX, float distanceY) {
-        // You don't use a scroller in onScroll because you don't need to animate
-        // a scroll. The scroll occurs instantly in response to touch feedback.
-        return false;
-    }
-
+    ...
     &#64;Override
     public boolean onFling(MotionEvent e1, MotionEvent e2, 
             float velocityX, float velocityY) {
-        // Before flinging, abort the current animation.
-        mScroller.forceFinished(true);
-        // Begin the scroll animation
-        mScroller.fling(
-                // Current scroll position
-                startX,
-                startY,
-                // Velocities, negated for natural touch response
-                (int) -velocityX,
-                (int) -velocityY,
-                // Minimum and maximum scroll positions. The minimum scroll 
-                // position is generally zero and the maximum scroll position 
-                // is generally the content size less the screen size. So if the 
-                // content width is 1000 pixels and the screen width is 200  
-                // pixels, the maximum scroll offset should be 800 pixels.
-                minX, maxX,
-                minY, maxY,
-                // The maximum overscroll bounds. This is useful when using
-                // the EdgeEffect class to draw overscroll "glow" overlays.
-                mContentRect.width() / 2,
-                mContentRect.height() / 2);
-        // Invalidate to trigger computeScroll()
-        postInvalidateOnAnimation();
+        fling((int) -velocityX, (int) -velocityY);
         return true;
     }
 };
 
+private void fling(int velocityX, int velocityY) {
+    // Initiates the decay phase of any active edge effects.
+    releaseEdgeEffects();
+    // Flings use math in pixels (as opposed to math based on the viewport).
+    Point surfaceSize = computeScrollSurfaceSize();
+    mScrollerStartViewport.set(mCurrentViewport);
+    int startX = (int) (surfaceSize.x * (mScrollerStartViewport.left - 
+            AXIS_X_MIN) / (
+            AXIS_X_MAX - AXIS_X_MIN));
+    int startY = (int) (surfaceSize.y * (AXIS_Y_MAX - 
+            mScrollerStartViewport.bottom) / (
+            AXIS_Y_MAX - AXIS_Y_MIN));
+    // Before flinging, aborts the current animation.
+    mScroller.forceFinished(true);
+    // Begins the animation
+    mScroller.fling(
+            // Current scroll position
+            startX,
+            startY,
+            velocityX,
+            velocityY,
+            /*
+             * Minimum and maximum scroll positions. The minimum scroll 
+             * position is generally zero and the maximum scroll position 
+             * is generally the content size less the screen size. So if the 
+             * content width is 1000 pixels and the screen width is 200  
+             * pixels, the maximum scroll offset should be 800 pixels.
+             */
+            0, surfaceSize.x - mContentRect.width(),
+            0, surfaceSize.y - mContentRect.height(),
+            // The edges of the content. This comes into play when using
+            // the EdgeEffect class to draw "glow" overlays.
+            mContentRect.width() / 2,
+            mContentRect.height() / 2);
+    // Invalidates to trigger computeScroll()
+    ViewCompat.postInvalidateOnAnimation(this);
+}</pre>
+
+<p>When {@link android.view.GestureDetector.OnGestureListener#onFling onFling()} calls 
+{@link android.support.v4.view.ViewCompat#postInvalidateOnAnimation postInvalidateOnAnimation()}, 
+it triggers 
+{@link android.view.View#computeScroll computeScroll()} to update the values for x and y. 
+This is typically be done when a view child is animating a scroll using a scroller object, as in this example. </p>
+
+<p>Most views pass the scroller object's x and y position directly to 
+{@link android.view.View#scrollTo scrollTo()}. 
+The following implementation of {@link android.view.View#computeScroll computeScroll()} 
+takes a different approach&mdash;it calls 
+{@link android.widget.OverScroller#computeScrollOffset computeScrollOffset()} to get the current 
+location of x and y. When the criteria for displaying an overscroll "glow" edge effect are met 
+(the display is zoomed in, x or y is out of bounds, and the app isn't already showing an overscroll), 
+the code sets up the overscroll glow effect and calls 
+{@link android.support.v4.view.ViewCompat#postInvalidateOnAnimation postInvalidateOnAnimation()} 
+to trigger an invalidate on the view:</p>
+
+<pre>// Edge effect / overscroll tracking objects.
+private EdgeEffectCompat mEdgeEffectTop;
+private EdgeEffectCompat mEdgeEffectBottom;
+private EdgeEffectCompat mEdgeEffectLeft;
+private EdgeEffectCompat mEdgeEffectRight;
+
+private boolean mEdgeEffectTopActive;
+private boolean mEdgeEffectBottomActive;
+private boolean mEdgeEffectLeftActive;
+private boolean mEdgeEffectRightActive;
+
 &#64;Override
 public void computeScroll() {
     super.computeScroll();
 
-    // Compute the current scroll offsets. If this returns true, then the 
-    // scroll has not yet finished.
+    boolean needsInvalidate = false;
+
+    // The scroller isn't finished, meaning a fling or programmatic pan 
+    // operation is currently active.
     if (mScroller.computeScrollOffset()) {
+        Point surfaceSize = computeScrollSurfaceSize();
         int currX = mScroller.getCurrX();
         int currY = mScroller.getCurrY();
 
-        // Actually render the scrolled viewport, or actually scroll the 
-        // view using View.scrollTo.
+        boolean canScrollX = (mCurrentViewport.left > AXIS_X_MIN
+                || mCurrentViewport.right < AXIS_X_MAX);
+        boolean canScrollY = (mCurrentViewport.top > AXIS_Y_MIN
+                || mCurrentViewport.bottom < AXIS_Y_MAX);
 
-        // If currX or currY are outside the bounds, render the overscroll 
-        // glow using EdgeEffect.
+        /*          
+         * If you are zoomed in and currX or currY is
+         * outside of bounds and you're not already
+         * showing overscroll, then render the overscroll
+         * glow edge effect.
+         */
+        if (canScrollX
+                && currX < 0
+                && mEdgeEffectLeft.isFinished()
+                && !mEdgeEffectLeftActive) {
+            mEdgeEffectLeft.onAbsorb((int) 
+                    OverScrollerCompat.getCurrVelocity(mScroller));
+            mEdgeEffectLeftActive = true;
+            needsInvalidate = true;
+        } else if (canScrollX
+                && currX > (surfaceSize.x - mContentRect.width())
+                && mEdgeEffectRight.isFinished()
+                && !mEdgeEffectRightActive) {
+            mEdgeEffectRight.onAbsorb((int) 
+                    OverScrollerCompat.getCurrVelocity(mScroller));
+            mEdgeEffectRightActive = true;
+            needsInvalidate = true;
+        }
 
-    } else {
-        // The scroll has finished.
-    }
+        if (canScrollY
+                && currY < 0
+                && mEdgeEffectTop.isFinished()
+                && !mEdgeEffectTopActive) {
+            mEdgeEffectTop.onAbsorb((int) 
+                    OverScrollerCompat.getCurrVelocity(mScroller));
+            mEdgeEffectTopActive = true;
+            needsInvalidate = true;
+        } else if (canScrollY
+                && currY > (surfaceSize.y - mContentRect.height())
+                && mEdgeEffectBottom.isFinished()
+                && !mEdgeEffectBottomActive) {
+            mEdgeEffectBottom.onAbsorb((int) 
+                    OverScrollerCompat.getCurrVelocity(mScroller));
+            mEdgeEffectBottomActive = true;
+            needsInvalidate = true;
+        }
+        ...
+    }</pre>
+
+<p>Here is the section of the code that performs the actual zoom:</p>
+
+<pre>// Custom object that is functionally similar to Scroller
+Zoomer mZoomer;
+private PointF mZoomFocalPoint = new PointF();
+...
+
+// If a zoom is in progress (either programmatically or via double
+// touch), performs the zoom.
+if (mZoomer.computeZoom()) {
+    float newWidth = (1f - mZoomer.getCurrZoom()) * 
+            mScrollerStartViewport.width();
+    float newHeight = (1f - mZoomer.getCurrZoom()) * 
+            mScrollerStartViewport.height();
+    float pointWithinViewportX = (mZoomFocalPoint.x - 
+            mScrollerStartViewport.left)
+            / mScrollerStartViewport.width();
+    float pointWithinViewportY = (mZoomFocalPoint.y - 
+            mScrollerStartViewport.top)
+            / mScrollerStartViewport.height();
+    mCurrentViewport.set(
+            mZoomFocalPoint.x - newWidth * pointWithinViewportX,
+            mZoomFocalPoint.y - newHeight * pointWithinViewportY,
+            mZoomFocalPoint.x + newWidth * (1 - pointWithinViewportX),
+            mZoomFocalPoint.y + newHeight * (1 - pointWithinViewportY));
+    constrainViewport();
+    needsInvalidate = true;
+}
+if (needsInvalidate) {
+    ViewCompat.postInvalidateOnAnimation(this);
+}
+</pre>
+
+<p>This is the {@code computeScrollSurfaceSize()} method that's called in the above snippet. It 
+computes the current scrollable surface size, in pixels. For example, if the entire chart area is visible, 
+this is simply the current size of {@code mContentRect}. If the chart is zoomed in 200% in both directions, 
+the returned size will be twice as large horizontally and vertically.</p>
+
+<pre>private Point computeScrollSurfaceSize() {
+    return new Point(
+            (int) (mContentRect.width() * (AXIS_X_MAX - AXIS_X_MIN)
+                    / mCurrentViewport.width()),
+            (int) (mContentRect.height() * (AXIS_Y_MAX - AXIS_Y_MIN)
+                    / mCurrentViewport.height()));
 }</pre>
 
-<p>For another example of scroller usage, see the <a href="http://github.com/android/platform_frameworks_support/blob/master/v4/java/android/support/v4/view/ViewPager.java">source code</a> for the 
-{@link android.support.v4.view.ViewPager} class.</p>
+<p>For another example of scroller usage, see the 
+<a href="http://github.com/android/platform_frameworks_support/blob/master/v4/java/android/support/v4/view/ViewPager.java">source code</a> for the 
+{@link android.support.v4.view.ViewPager} class. It scrolls in response to flings, 
+and uses scrolling to implement the "snapping to page" animation.</p>
+
diff --git a/docs/html/training/gestures/viewgroup.jd b/docs/html/training/gestures/viewgroup.jd
index 257a5d8..5b32300 100644
--- a/docs/html/training/gestures/viewgroup.jd
+++ b/docs/html/training/gestures/viewgroup.jd
@@ -26,12 +26,19 @@
     <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
+
 
 </div>
 </div>
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 85b2052..1618110 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -7,6 +7,7 @@
 	LOCAL_SRC_FILES:= \
 		utils/Blur.cpp \
 		utils/SortedListImpl.cpp \
+		thread/TaskManager.cpp \
 		font/CacheTexture.cpp \
 		font/Font.cpp \
 		FontRenderer.cpp \
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index ca699d5..dc32a7e 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -25,6 +25,9 @@
 
 #include <cutils/compiler.h>
 
+#include "thread/TaskProcessor.h"
+#include "thread/TaskManager.h"
+
 #include "FontRenderer.h"
 #include "GammaFontRenderer.h"
 #include "TextureCache.h"
@@ -278,6 +281,8 @@
 
     GammaFontRenderer* fontRenderer;
 
+    TaskManager tasks;
+
     Dither dither;
     Stencil stencil;
 
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 9e6ec84..afdc2c9 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -31,69 +31,32 @@
 // Path precaching
 ///////////////////////////////////////////////////////////////////////////////
 
-bool PathCache::PrecacheThread::threadLoop() {
-    mSignal.wait();
-    Vector<Task> tasks;
-    {
-        Mutex::Autolock l(mLock);
-        tasks = mTasks;
-        mTasks.clear();
-    }
-
-    Caches& caches = Caches::getInstance();
-    uint32_t maxSize = caches.maxTextureSize;
-
-    ATRACE_BEGIN("pathPrecache");
-    for (size_t i = 0; i < tasks.size(); i++) {
-        const Task& task = tasks.itemAt(i);
-
-        float left, top, offset;
-        uint32_t width, height;
-        PathCache::computePathBounds(task.path, task.paint, left, top, offset, width, height);
-
-        if (width <= maxSize && height <= maxSize) {
-            SkBitmap* bitmap = new SkBitmap();
-
-            PathTexture* texture = task.texture;
-            texture->left = left;
-            texture->top = top;
-            texture->offset = offset;
-            texture->width = width;
-            texture->height = height;
-
-            PathCache::drawPath(task.path, task.paint, *bitmap, left, top, offset, width, height);
-
-            texture->future()->produce(bitmap);
-        } else {
-            task.texture->future()->produce(NULL);
-        }
-    }
-    ATRACE_END();
-    return true;
+PathCache::PathProcessor::PathProcessor(Caches& caches):
+        TaskProcessor<SkBitmap*>(&caches.tasks), mMaxTextureSize(caches.maxTextureSize) {
 }
 
-void PathCache::PrecacheThread::addTask(PathTexture* texture, SkPath* path, SkPaint* paint) {
-    if (!isRunning()) {
-        run("libhwui:pathPrecache", PRIORITY_DEFAULT);
+void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) {
+    sp<PathTask> t = static_cast<PathTask* >(task.get());
+    ATRACE_NAME("pathPrecache");
+
+    float left, top, offset;
+    uint32_t width, height;
+    PathCache::computePathBounds(t->path, t->paint, left, top, offset, width, height);
+
+    PathTexture* texture = t->texture;
+    texture->left = left;
+    texture->top = top;
+    texture->offset = offset;
+    texture->width = width;
+    texture->height = height;
+
+    if (width <= mMaxTextureSize && height <= mMaxTextureSize) {
+        SkBitmap* bitmap = new SkBitmap();
+        PathCache::drawPath(t->path, t->paint, *bitmap, left, top, offset, width, height);
+        t->setResult(bitmap);
+    } else {
+        t->setResult(NULL);
     }
-
-    Task task;
-    task.texture = texture;
-    task.path = path;
-    task.paint = paint;
-
-    Mutex::Autolock l(mLock);
-    mTasks.add(task);
-    mSignal.signal();
-}
-
-void PathCache::PrecacheThread::exit() {
-    {
-        Mutex::Autolock l(mLock);
-        mTasks.clear();
-    }
-    requestExit();
-    mSignal.signal();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -101,11 +64,10 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 PathCache::PathCache(): ShapeCache<PathCacheEntry>("path",
-        PROPERTY_PATH_CACHE_SIZE, DEFAULT_PATH_CACHE_SIZE), mThread(new PrecacheThread()) {
+        PROPERTY_PATH_CACHE_SIZE, DEFAULT_PATH_CACHE_SIZE) {
 }
 
 PathCache::~PathCache() {
-    mThread->exit();
 }
 
 void PathCache::remove(SkPath* path) {
@@ -165,17 +127,18 @@
     } else {
         // A bitmap is attached to the texture, this means we need to
         // upload it as a GL texture
-        if (texture->future() != NULL) {
+        const sp<Task<SkBitmap*> >& task = texture->task();
+        if (task != NULL) {
             // But we must first wait for the worker thread to be done
             // producing the bitmap, so let's wait
-            SkBitmap* bitmap = texture->future()->get();
+            SkBitmap* bitmap = task->getResult();
             if (bitmap) {
                 addTexture(entry, bitmap, texture);
-                texture->clearFuture();
+                texture->clearTask();
             } else {
                 ALOGW("Path too large to be rendered into a texture (%dx%d)",
                         texture->width, texture->height);
-                texture->clearFuture();
+                texture->clearTask();
                 texture = NULL;
                 mCache.remove(entry);
             }
@@ -189,6 +152,10 @@
 }
 
 void PathCache::precache(SkPath* path, SkPaint* paint) {
+    if (!Caches::getInstance().tasks.canRunTasks()) {
+        return;
+    }
+
     path = getSourcePath(path);
 
     PathCacheEntry entry(path, paint);
@@ -205,7 +172,9 @@
     if (generate) {
         // It is important to specify the generation ID so we do not
         // attempt to precache the same path several times
-        texture = createTexture(0.0f, 0.0f, 0.0f, 0, 0, path->getGenerationID(), true);
+        texture = createTexture(0.0f, 0.0f, 0.0f, 0, 0, path->getGenerationID());
+        sp<PathTask> task = new PathTask(path, paint, texture);
+        texture->setTask(task);
 
         // During the precaching phase we insert path texture objects into
         // the cache that do not point to any GL texture. They are instead
@@ -215,7 +184,11 @@
         // asks for a path texture. This is also when the cache limit will
         // be enforced.
         mCache.put(entry, texture);
-        mThread->addTask(texture, path, paint);
+
+        if (mProcessor == NULL) {
+            mProcessor = new PathProcessor(Caches::getInstance());
+        }
+        mProcessor->add(task);
     }
 }
 
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
index 1d28ecb..27031a5 100644
--- a/libs/hwui/PathCache.h
+++ b/libs/hwui/PathCache.h
@@ -23,6 +23,8 @@
 #include "Debug.h"
 #include "ShapeCache.h"
 #include "thread/Signal.h"
+#include "thread/Task.h"
+#include "thread/TaskProcessor.h"
 
 class SkPaint;
 class SkPath;
@@ -100,32 +102,33 @@
     void precache(SkPath* path, SkPaint* paint);
 
 private:
-    class PrecacheThread: public Thread {
+    class PathTask: public Task<SkBitmap*> {
     public:
-        PrecacheThread(): mSignal(Condition::WAKE_UP_ONE) { }
+        PathTask(SkPath* path, SkPaint* paint, PathTexture* texture):
+            path(path), paint(paint), texture(texture) {
+        }
 
-        void addTask(PathTexture* texture, SkPath* path, SkPaint* paint);
-        void exit();
+        ~PathTask() {
+            delete future()->get();
+        }
 
-    private:
-        struct Task {
-            PathTexture* texture;
-            SkPath* path;
-            SkPaint* paint;
-        };
-
-        virtual bool threadLoop();
-
-        // Lock for the list of tasks
-        Mutex mLock;
-        Vector<Task> mTasks;
-
-        // Signal used to wake up the thread when a new
-        // task is available in the list
-        mutable Signal mSignal;
+        SkPath* path;
+        SkPaint* paint;
+        PathTexture* texture;
     };
 
-    sp<PrecacheThread> mThread;
+    class PathProcessor: public TaskProcessor<SkBitmap*> {
+    public:
+        PathProcessor(Caches& caches);
+        ~PathProcessor() { }
+
+        virtual void onProcess(const sp<Task<SkBitmap*> >& task);
+
+    private:
+        uint32_t mMaxTextureSize;
+    };
+
+    sp<PathProcessor> mProcessor;
     Vector<SkPath*> mGarbage;
     mutable Mutex mLock;
 }; // class PathCache
diff --git a/libs/hwui/ShapeCache.h b/libs/hwui/ShapeCache.h
index 67ae85b..58fea08 100644
--- a/libs/hwui/ShapeCache.h
+++ b/libs/hwui/ShapeCache.h
@@ -30,12 +30,11 @@
 #include <utils/JenkinsHash.h>
 #include <utils/LruCache.h>
 #include <utils/Trace.h>
-#include <utils/CallStack.h>
 
 #include "Debug.h"
 #include "Properties.h"
 #include "Texture.h"
-#include "thread/Future.h"
+#include "thread/Task.h"
 
 namespace android {
 namespace uirenderer {
@@ -62,14 +61,8 @@
     PathTexture(): Texture() {
     }
 
-    PathTexture(bool hasFuture): Texture() {
-        if (hasFuture) {
-            mFuture = new Future<SkBitmap*>();
-        }
-    }
-
     ~PathTexture() {
-        clearFuture();
+        clearTask();
     }
 
     /**
@@ -85,19 +78,22 @@
      */
     float offset;
 
-    sp<Future<SkBitmap*> > future() const {
-        return mFuture;
+    sp<Task<SkBitmap*> > task() const {
+        return mTask;
     }
 
-    void clearFuture() {
-        if (mFuture != NULL) {
-            delete mFuture->get();
-            mFuture.clear();
+    void setTask(const sp<Task<SkBitmap*> >& task) {
+        mTask = task;
+    }
+
+    void clearTask() {
+        if (mTask != NULL) {
+            mTask.clear();
         }
     }
 
 private:
-    sp<Future<SkBitmap*> > mFuture;
+    sp<Task<SkBitmap*> > mTask;
 }; // struct PathTexture
 
 /**
@@ -551,8 +547,8 @@
     }
 
     static PathTexture* createTexture(float left, float top, float offset,
-            uint32_t width, uint32_t height, uint32_t id, bool hasFuture = false) {
-        PathTexture* texture = new PathTexture(hasFuture);
+            uint32_t width, uint32_t height, uint32_t id) {
+        PathTexture* texture = new PathTexture();
         texture->left = left;
         texture->top = top;
         texture->offset = offset;
diff --git a/libs/hwui/thread/Future.h b/libs/hwui/thread/Future.h
index 340fec7..a3ff3bc 100644
--- a/libs/hwui/thread/Future.h
+++ b/libs/hwui/thread/Future.h
@@ -24,7 +24,7 @@
 namespace android {
 namespace uirenderer {
 
-template<class T>
+template<typename T>
 class Future: public LightRefBase<Future<T> > {
 public:
     Future(Condition::WakeUpType type = Condition::WAKE_UP_ONE): mBarrier(type), mResult() { }
diff --git a/libs/hwui/thread/Task.h b/libs/hwui/thread/Task.h
new file mode 100644
index 0000000..9a211a2
--- /dev/null
+++ b/libs/hwui/thread/Task.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#ifndef ANDROID_HWUI_TASK_H
+#define ANDROID_HWUI_TASK_H
+
+#define ATRACE_TAG ATRACE_TAG_VIEW
+
+#include <utils/RefBase.h>
+#include <utils/Trace.h>
+
+#include "Future.h"
+
+namespace android {
+namespace uirenderer {
+
+class TaskBase: public RefBase {
+public:
+    TaskBase() { }
+    virtual ~TaskBase() { }
+};
+
+template<typename T>
+class Task: public TaskBase {
+public:
+    Task(): mFuture(new Future<T>()) { }
+    virtual ~Task() { }
+
+    T getResult() const {
+        ATRACE_NAME("waitForTask");
+        return mFuture->get();
+    }
+
+    void setResult(T result) {
+        mFuture->produce(result);
+    }
+
+protected:
+    const sp<Future<T> >& future() const {
+        return mFuture;
+    }
+
+private:
+    sp<Future<T> > mFuture;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_TASK_H
diff --git a/libs/hwui/thread/TaskManager.cpp b/libs/hwui/thread/TaskManager.cpp
new file mode 100644
index 0000000..ce6c8c0
--- /dev/null
+++ b/libs/hwui/thread/TaskManager.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#include <sys/sysinfo.h>
+
+#include "Task.h"
+#include "TaskProcessor.h"
+#include "TaskManager.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Manager
+///////////////////////////////////////////////////////////////////////////////
+
+TaskManager::TaskManager() {
+    // Get the number of available CPUs. This value does not change over time.
+    int cpuCount = sysconf(_SC_NPROCESSORS_ONLN);
+
+    for (int i = 0; i < cpuCount / 2; i++) {
+        String8 name;
+        name.appendFormat("hwuiTask%d", i + 1);
+        mThreads.add(new WorkerThread(name));
+    }
+}
+
+TaskManager::~TaskManager() {
+    for (size_t i = 0; i < mThreads.size(); i++) {
+        mThreads[i]->exit();
+    }
+}
+
+bool TaskManager::canRunTasks() const {
+    return mThreads.size() > 0;
+}
+
+bool TaskManager::addTaskBase(const sp<TaskBase>& task, const sp<TaskProcessorBase>& processor) {
+    if (mThreads.size() > 0) {
+        TaskWrapper wrapper(task, processor);
+
+        size_t minQueueSize = INT_MAX;
+        sp<WorkerThread> thread;
+
+        for (size_t i = 0; i < mThreads.size(); i++) {
+            if (mThreads[i]->getTaskCount() < minQueueSize) {
+                thread = mThreads[i];
+                minQueueSize = mThreads[i]->getTaskCount();
+            }
+        }
+
+        return thread->addTask(wrapper);
+    }
+    return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Thread
+///////////////////////////////////////////////////////////////////////////////
+
+bool TaskManager::WorkerThread::threadLoop() {
+    mSignal.wait();
+    Vector<TaskWrapper> tasks;
+    {
+        Mutex::Autolock l(mLock);
+        tasks = mTasks;
+        mTasks.clear();
+    }
+
+    for (size_t i = 0; i < tasks.size(); i++) {
+        const TaskWrapper& task = tasks.itemAt(i);
+        task.mProcessor->process(task.mTask);
+    }
+
+    return true;
+}
+
+bool TaskManager::WorkerThread::addTask(TaskWrapper task) {
+    if (!isRunning()) {
+        run(mName.string(), PRIORITY_DEFAULT);
+    }
+
+    Mutex::Autolock l(mLock);
+    ssize_t index = mTasks.add(task);
+    mSignal.signal();
+
+    return index >= 0;
+}
+
+size_t TaskManager::WorkerThread::getTaskCount() const {
+    Mutex::Autolock l(mLock);
+    return mTasks.size();
+}
+
+void TaskManager::WorkerThread::exit() {
+    {
+        Mutex::Autolock l(mLock);
+        mTasks.clear();
+    }
+    requestExit();
+    mSignal.signal();
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/thread/TaskManager.h b/libs/hwui/thread/TaskManager.h
new file mode 100644
index 0000000..bc86062
--- /dev/null
+++ b/libs/hwui/thread/TaskManager.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#ifndef ANDROID_HWUI_TASK_MANAGER_H
+#define ANDROID_HWUI_TASK_MANAGER_H
+
+#include <utils/Mutex.h>
+#include <utils/String8.h>
+#include <utils/Thread.h>
+#include <utils/Vector.h>
+
+#include "Signal.h"
+
+namespace android {
+namespace uirenderer {
+
+template <typename T>
+class Task;
+class TaskBase;
+
+template <typename T>
+class TaskProcessor;
+class TaskProcessorBase;
+
+class TaskManager {
+public:
+    TaskManager();
+    ~TaskManager();
+
+    /**
+     * Returns true if this task  manager can run tasks,
+     * false otherwise. This method will typically return
+     * true on a single CPU core device.
+     */
+    bool canRunTasks() const;
+
+private:
+    template <typename T>
+    friend class TaskProcessor;
+
+    template<typename T>
+    bool addTask(const sp<Task<T> >& task, const sp<TaskProcessor<T> >& processor) {
+        return addTaskBase(sp<TaskBase>(task), sp<TaskProcessorBase>(processor));
+    }
+
+    bool addTaskBase(const sp<TaskBase>& task, const sp<TaskProcessorBase>& processor);
+
+    struct TaskWrapper {
+        TaskWrapper(): mTask(), mProcessor() { }
+
+        TaskWrapper(const sp<TaskBase>& task, const sp<TaskProcessorBase>& processor):
+            mTask(task), mProcessor(processor) {
+        }
+
+        sp<TaskBase> mTask;
+        sp<TaskProcessorBase> mProcessor;
+    };
+
+    class WorkerThread: public Thread {
+    public:
+        WorkerThread(const String8 name): mSignal(Condition::WAKE_UP_ONE), mName(name) { }
+
+        bool addTask(TaskWrapper task);
+        size_t getTaskCount() const;
+        void exit();
+
+    private:
+        virtual bool threadLoop();
+
+        // Lock for the list of tasks
+        mutable Mutex mLock;
+        Vector<TaskWrapper> mTasks;
+
+        // Signal used to wake up the thread when a new
+        // task is available in the list
+        mutable Signal mSignal;
+
+        const String8 mName;
+    };
+
+    Vector<sp<WorkerThread> > mThreads;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_TASK_MANAGER_H
diff --git a/libs/hwui/thread/TaskProcessor.h b/libs/hwui/thread/TaskProcessor.h
new file mode 100644
index 0000000..d1269f0
--- /dev/null
+++ b/libs/hwui/thread/TaskProcessor.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#ifndef ANDROID_HWUI_TASK_PROCESSOR_H
+#define ANDROID_HWUI_TASK_PROCESSOR_H
+
+#include <utils/RefBase.h>
+
+#include "Task.h"
+#include "TaskManager.h"
+
+namespace android {
+namespace uirenderer {
+
+class TaskProcessorBase: public RefBase {
+public:
+    TaskProcessorBase() { }
+    virtual ~TaskProcessorBase() { };
+
+private:
+    friend class TaskManager;
+
+    virtual void process(const sp<TaskBase>& task) = 0;
+};
+
+template<typename T>
+class TaskProcessor: public TaskProcessorBase {
+public:
+    TaskProcessor(TaskManager* manager): mManager(manager) { }
+    virtual ~TaskProcessor() { }
+
+    bool add(const sp<Task<T> >& task);
+
+    virtual void onProcess(const sp<Task<T> >& task) = 0;
+
+private:
+    virtual void process(const sp<TaskBase>& task) {
+        sp<Task<T> > realTask = static_cast<Task<T>* >(task.get());
+        // This is the right way to do it but sp<> doesn't play nice
+        // sp<Task<T> > realTask = static_cast<sp<Task<T> > >(task);
+        onProcess(realTask);
+    }
+
+    TaskManager* mManager;
+};
+
+template<typename T>
+bool TaskProcessor<T>::add(const sp<Task<T> >& task) {
+    if (mManager) {
+        sp<TaskProcessor<T> > self(this);
+        return mManager->addTask(task, self);
+    }
+    return false;
+}
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_TASK_PROCESSOR_H
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 882a635..f0a5c28 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -1948,8 +1948,13 @@
                 !mBootCompleted) {
             return;
         }
-        final long ident = Binder.clearCallingIdentity();
         ScoClient client = getScoClient(cb, true);
+        // The calling identity must be cleared before calling ScoClient.incCount().
+        // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
+        // and this must be done on behalf of system server to make sure permissions are granted.
+        // The caller identity must be cleared after getScoClient() because it is needed if a new
+        // client is created.
+        final long ident = Binder.clearCallingIdentity();
         client.incCount();
         Binder.restoreCallingIdentity(ident);
     }
@@ -1960,8 +1965,11 @@
                 !mBootCompleted) {
             return;
         }
-        final long ident = Binder.clearCallingIdentity();
         ScoClient client = getScoClient(cb, false);
+        // The calling identity must be cleared before calling ScoClient.decCount().
+        // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
+        // and this must be done on behalf of system server to make sure permissions are granted.
+        final long ident = Binder.clearCallingIdentity();
         if (client != null) {
             client.decCount();
         }
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 f0b1d7e..9f54573 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -407,6 +407,7 @@
         mSystemIconArea = (LinearLayout) mStatusBarView.findViewById(R.id.system_icon_area);
         mStatusIcons = (LinearLayout)mStatusBarView.findViewById(R.id.statusIcons);
         mNotificationIcons = (IconMerger)mStatusBarView.findViewById(R.id.notificationIcons);
+        mMoreIcon = mStatusBarView.findViewById(R.id.moreIcon);
         mNotificationIcons.setOverflowIndicator(mMoreIcon);
         mStatusBarContents = (LinearLayout)mStatusBarView.findViewById(R.id.status_bar_contents);
         mTickerView = mStatusBarView.findViewById(R.id.ticker);
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 6ba5cff..01f7544 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -1243,6 +1243,10 @@
                     unbindCurrentMethodLocked(false, false);
                     return;
                 }
+                // Remove commands relating to the previous service. Otherwise WindowManagerService
+                // will reject the command because the token attached to these messages is invalid.
+                mCaller.removeMessages(MSG_SHOW_SOFT_INPUT);
+                mCaller.removeMessages(MSG_HIDE_SOFT_INPUT);
                 if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
                 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
                         MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java
index ac8ab1f..9f97311 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java
@@ -33,6 +33,7 @@
     private Path mPath;
 
     private final Random mRandom = new Random();
+    @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
     private final ArrayList<Path> mPathList = new ArrayList<Path>();
 
     @Override
@@ -58,6 +59,19 @@
         path.cubicTo(-80.0f, 200.0f, 100.0f, 200.0f, 200.0f, 0.0f);
     }
 
+    private static Path makeLargePath() {
+        Path path = new Path();
+        buildLargePath(path);
+        return path;
+    }
+
+    private static void buildLargePath(Path path) {
+        path.moveTo(0.0f, 0.0f);
+        path.cubicTo(0.0f, 0.0f, 10000.0f, 15000.0f, 10000.0f, 20000.0f);
+        path.cubicTo(10000.0f, 20000.0f, 5000.0f, 30000.0f, -8000.0f, 20000.0f);
+        path.cubicTo(-8000.0f, 20000.0f, 10000.0f, 20000.0f, 20000.0f, 0.0f);
+    }
+
     public class PathsView extends View {
         private final Paint mMediumPaint;
 
@@ -97,6 +111,9 @@
                 int r = mRandom.nextInt(10);
                 if (r == 5 || r == 3) {
                     mPathList.add(path);
+                } else if (r == 7) {
+                    path = makeLargePath();
+                    mPathList.add(path);
                 }
     
                 canvas.save();