Merge "remove uses of Surface in favor of IGraphicBufferProducer" into jb-mr2-dev
diff --git a/api/current.txt b/api/current.txt
index 3132a91..be104a0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5919,7 +5919,6 @@
     field public static final java.lang.String ACTION_USER_INITIALIZE = "android.intent.action.USER_INITIALIZE";
     field public static final java.lang.String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT";
     field public static final java.lang.String ACTION_VIEW = "android.intent.action.VIEW";
-    field public static final java.lang.String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
     field public static final java.lang.String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
     field public static final deprecated java.lang.String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
     field public static final java.lang.String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH";
@@ -10046,16 +10045,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 +10080,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 +10096,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 +10160,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/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
index 18492ab..f44dc5c0 100644
--- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java
+++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
@@ -529,7 +529,8 @@
             case FORMAT_UINT32:
                 mValue[offset++] = (byte)(value & 0xFF);
                 mValue[offset++] = (byte)((value >> 8) & 0xFF);
-                mValue[offset] = (byte)((value >> 16) & 0xFF);
+                mValue[offset++] = (byte)((value >> 16) & 0xFF);
+                mValue[offset] = (byte)((value >> 24) & 0xFF);
                 break;
 
             default:
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index 91a1a94..6b69377 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -534,7 +534,7 @@
      *
      * <p>The connection may not be established right away, but will be
      * completed when the remote device is available. A
-     * {@link BluetoothGattCallback#onConnectionStateChange} callback will be
+     * {@link BluetoothGattServerCallback#onConnectionStateChange} callback will be
      * invoked when the connection state changes as a result of this function.
      *
      * <p>The autoConnect paramter determines whether to actively connect to
@@ -553,7 +553,7 @@
      * @return true, if the connection attempt was initiated successfully
      */
     public boolean connect(BluetoothDevice device, boolean autoConnect) {
-        if (DBG) Log.d(TAG, "connect: " + device.getAddress() + ", auto: " + autoConnect);
+        if (DBG) Log.d(TAG, "connect() - device: " + device.getAddress() + ", auto: " + autoConnect);
         if (mService == null || mServerIf == 0) return false;
 
         try {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 60e9f58..53c47d2 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1164,12 +1164,13 @@
      * additional optional contextual information about where the user was when they requested
      * the voice assist.
      * Output: nothing.
+     * @hide
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
 
     /**
-     * An optional field on {@link #ACTION_ASSIST} and {@link #ACTION_VOICE_ASSIST}
+     * An optional field on {@link #ACTION_ASSIST}
      * containing the name of the current foreground application package at the time
      * the assist was invoked.
      */
@@ -1177,7 +1178,7 @@
             = "android.intent.extra.ASSIST_PACKAGE";
 
     /**
-     * An optional field on {@link #ACTION_ASSIST} and {@link #ACTION_VOICE_ASSIST}
+     * An optional field on {@link #ACTION_ASSIST}
      * containing additional contextual information supplied by the current
      * foreground app at the time of the assist request.  This is a {@link Bundle} of
      * additional data.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c507245..0d463ee 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -691,6 +691,17 @@
     public static final int DELETE_ALL_USERS = 0x00000002;
 
     /**
+     * Flag parameter for {@link #deletePackage} to indicate that, if you are calling
+     * uninstall on a system that has been updated, then don't do the normal process
+     * of uninstalling the update and rolling back to the older system version (which
+     * needs to happen for all users); instead, just mark the app as uninstalled for
+     * the current user.
+     *
+     * @hide
+     */
+    public static final int DELETE_SYSTEM_APP = 0x00000004;
+
+    /**
      * Return code for when package deletion succeeds. This is passed to the
      * {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
      * succeeded in deleting the package.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index e1887bc..5eac903 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1941,6 +1941,28 @@
                     return false;
                 }
 
+            } else if (tagName.equals("library")) {
+                sa = res.obtainAttributes(attrs,
+                        com.android.internal.R.styleable.AndroidManifestLibrary);
+
+                // Note: don't allow this value to be a reference to a resource
+                // that may change.
+                String lname = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestLibrary_name);
+
+                sa.recycle();
+
+                if (lname != null) {
+                    if (owner.libraryNames == null) {
+                        owner.libraryNames = new ArrayList<String>();
+                    }
+                    if (!owner.libraryNames.contains(lname)) {
+                        owner.libraryNames.add(lname.intern());
+                    }
+                }
+
+                XmlUtils.skipCurrentTag(parser);
+
             } else if (tagName.equals("uses-library")) {
                 sa = res.obtainAttributes(attrs,
                         com.android.internal.R.styleable.AndroidManifestUsesLibrary);
@@ -3182,7 +3204,8 @@
         public final ArrayList<Boolean> requestedPermissionsRequired = new ArrayList<Boolean>();
 
         public ArrayList<String> protectedBroadcasts;
-        
+
+        public ArrayList<String> libraryNames = null;
         public ArrayList<String> usesLibraries = null;
         public ArrayList<String> usesOptionalLibraries = null;
         public String[] usesLibraryFiles = null;
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..893d701 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>(
+                        Math.max(sFullSensorsList.size()*2, 1));
+                sTriggerEventPool = new Pools.SynchronizedPool<TriggerEvent>(
+                        Math.max(sFullSensorsList.size()*2, 1));
             }
         }
     }
@@ -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..76b2796
--- /dev/null
+++ b/core/java/android/hardware/TriggerEventListener.java
@@ -0,0 +1,78 @@
+/*
+ * 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 {
+ *     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/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 99624cc..288ceff 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -39,6 +39,7 @@
 import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
+import android.util.Slog;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -351,6 +352,7 @@
          * Take care of attaching the given window token provided by the system.
          */
         public void attachToken(IBinder token) {
+            Slog.i(TAG, "attachToken: Existing token=" + mToken + " new token=" + token);
             if (mToken == null) {
                 mToken = token;
                 mWindow.setToken(token);
@@ -417,7 +419,7 @@
          * Handle a request by the system to show the soft input area.
          */
         public void showSoftInput(int flags, ResultReceiver resultReceiver) {
-            if (DEBUG) Log.v(TAG, "showSoftInput()");
+            if (true || DEBUG) Slog.v(TAG, "showSoftInput()");
             boolean wasVis = isInputViewShown();
             mShowInputFlags = 0;
             if (onShowInputRequested(flags, false)) {
@@ -1388,7 +1390,7 @@
     }
     
     public void showWindow(boolean showInput) {
-        if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput
+        if (true || DEBUG) Slog.v(TAG, "Showing window: showInput=" + showInput
                 + " mShowInputRequested=" + mShowInputRequested
                 + " mWindowAdded=" + mWindowAdded
                 + " mWindowCreated=" + mWindowCreated
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/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 4379418..703dcff 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -810,9 +810,15 @@
                 return TextToSpeech.ERROR;
             }
 
+            // In test env, ParcelFileDescriptor instance may be EXACTLY the same
+            // one that is used by client. And it will be closed by a client, thus
+            // preventing us from writing anything to it.
+            final ParcelFileDescriptor sameFileDescriptor = ParcelFileDescriptor.adoptFd(
+                    fileDescriptor.detachFd());
+
             SpeechItem item = new SynthesisToFileOutputStreamSpeechItem(caller,
                     Binder.getCallingUid(), Binder.getCallingPid(), params, text,
-                    new ParcelFileDescriptor.AutoCloseOutputStream(fileDescriptor));
+                    new ParcelFileDescriptor.AutoCloseOutputStream(sameFileDescriptor));
             return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
         }
 
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/java/com/android/internal/view/IInputConnectionCallback.aidl b/core/java/com/android/internal/view/IInputConnectionCallback.aidl
deleted file mode 100644
index 5b5b3df..0000000
--- a/core/java/com/android/internal/view/IInputConnectionCallback.aidl
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2008 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 com.android.internal.view;
-
-import android.graphics.Rect;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.inputmethod.TextBoxAttribute;
-import com.android.internal.view.IInputContext;
-import android.os.IBinder;
-
-/**
- * {@hide}
- */
-oneway interface IInputMethodCallback {
-    void finishedEvent(int seq, boolean handled);
-}
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/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 95e0b5b..2fc7f4a 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -329,7 +329,7 @@
     <string name="permdesc_confirm_full_backup" msgid="1748762171637699562">"Ermöglicht der App, die Benutzeroberfläche zur Bestätigung der vollständigen Sicherung zu starten. Kann nicht von jeder App verwendet werden."</string>
     <string name="permlab_internalSystemWindow" msgid="2148563628140193231">"Nicht autorisierte Fenster anzeigen"</string>
     <string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"Ermöglicht der App die Erstellung von Fenstern, die von der Benutzeroberfläche des internen Systems verwendet werden. Nicht für normale Apps vorgesehen."</string>
-    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"Über andere Apps zeichnen"</string>
+    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"Über anderen Apps einblenden"</string>
     <string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"Ermöglicht der App, über andere Apps oder Teile der Benutzeroberfläche zu zeichnen. Dies kann sich auf die Oberfläche in jeder App auswirken oder die erwartete Darstellung in anderen Apps verändern."</string>
     <string name="permlab_setAnimationScale" msgid="2805103241153907174">"Allgemeine Animationsgeschwindigkeit einstellen"</string>
     <string name="permdesc_setAnimationScale" msgid="7690063428924343571">"Ermöglicht der App, die allgemeine Animationsgeschwindigkeit (langsamere oder schnellere Animationen) jederzeit anzupassen."</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 985b088..1fc59b6 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -182,7 +182,7 @@
     <string name="permgroupdesc_bluetoothNetwork" msgid="5625288577164282391">"Acceder a dispositivos y redes a través de Bluetooth"</string>
     <string name="permgrouplab_audioSettings" msgid="8329261670151871235">"Configuración de audio"</string>
     <string name="permgroupdesc_audioSettings" msgid="2641515403347568130">"Cambiar la configuración de audio"</string>
-    <string name="permgrouplab_affectsBattery" msgid="6209246653424798033">"Afecta la batería."</string>
+    <string name="permgrouplab_affectsBattery" msgid="6209246653424798033">"Afecta la batería"</string>
     <string name="permgroupdesc_affectsBattery" msgid="6441275320638916947">"Uso de las características que se pueden agotar rápidamente la batería"</string>
     <string name="permgrouplab_calendar" msgid="5863508437783683902">"Calendario"</string>
     <string name="permgroupdesc_calendar" msgid="5777534316982184416">"Acceso directo a calendario y eventos"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 5a701a0..86ead94 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -329,7 +329,7 @@
     <string name="permdesc_confirm_full_backup" msgid="1748762171637699562">"Permite que a aplicação inicie a IU de confirmação de cópia de segurança completa. Não deve ser utilizado por qualquer aplicação."</string>
     <string name="permlab_internalSystemWindow" msgid="2148563628140193231">"apresentar janelas não autorizadas"</string>
     <string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"Permite que a aplicação crie janelas que se destinam a ser utilizadas ​​pela interface de utilizador do sistema interno. Nunca é necessário para aplicações normais."</string>
-    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"desenhar sobre outras aplicações"</string>
+    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"mostrar sobre outras aplicações"</string>
     <string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"Permite que a aplicação se sobreponha a outras aplicações ou partes da interface de utilizador. Poderá interferir na utilização da interface de qualquer aplicação ou alterar o que pensa estar a ver noutras aplicações."</string>
     <string name="permlab_setAnimationScale" msgid="2805103241153907174">"modificar velocidade global da animação"</string>
     <string name="permdesc_setAnimationScale" msgid="7690063428924343571">"Permite que a aplicação altere a velocidade global da animação (animações mais rápidas ou mais lentas) em qualquer altura."</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 9eb11b2..4650d55 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -329,7 +329,7 @@
     <string name="permdesc_confirm_full_backup" msgid="1748762171637699562">"Permite que o aplicativo lance a interface de usuário de confirmação de backup completo. Não deve ser usado por qualquer aplicativo."</string>
     <string name="permlab_internalSystemWindow" msgid="2148563628140193231">"exibir janelas não autorizadas"</string>
     <string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"Permite que o aplicativo crie janelas destinadas ao uso ​​pela interface interna do sistema. Não deve ser usado em aplicativos normais."</string>
-    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"induzir outros aplicativos"</string>
+    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"sobrepor outros aplicativos"</string>
     <string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"Permite que o aplicativo se sobreponha visualmente a outros aplicativos ou a partes da interface do usuário. Podem interferir com o uso da interface de qualquer aplicativo ou alterar o que você acha que está vendo em outros aplicativos."</string>
     <string name="permlab_setAnimationScale" msgid="2805103241153907174">"modificar velocidade de animação global"</string>
     <string name="permdesc_setAnimationScale" msgid="7690063428924343571">"Permite que o aplicativo altere a velocidade de animação global (animação mais rápida ou mais lenta) a qualquer momento."</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index eb7185f..88c56bc 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1200,8 +1200,8 @@
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formataţi"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Depanarea USB este conectată"</string>
     <string name="adb_active_notification_message" msgid="1016654627626476142">"Atingeţi pentru a dezactiva depanarea USB."</string>
-    <string name="select_input_method" msgid="4653387336791222978">"Alegeţi metoda de introducere"</string>
-    <string name="configure_input_methods" msgid="9091652157722495116">"Configurare metode introducere"</string>
+    <string name="select_input_method" msgid="4653387336791222978">"Alegeți metoda de introducere de text"</string>
+    <string name="configure_input_methods" msgid="9091652157722495116">"Configurați metode introducere"</string>
     <string name="use_physical_keyboard" msgid="6203112478095117625">"Tastatură fizică"</string>
     <string name="hardware" msgid="7517821086888990278">"Hardware"</string>
     <string name="select_keyboard_layout_notification_title" msgid="1407367017263030773">"Selectaţi aspectul tastaturii"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index b8b1c29..9475472 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -329,7 +329,7 @@
     <string name="permdesc_confirm_full_backup" msgid="1748762171637699562">"Приложение сможет отображать окно подтверждения полного резервного копирования. Это разрешение не предназначено для всех приложений."</string>
     <string name="permlab_internalSystemWindow" msgid="2148563628140193231">"показывать неавторизованные окна"</string>
     <string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"Приложение сможет создавать окна для интерфейса внутренней системы. Это разрешение не используется обычными приложениями."</string>
-    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"Показ сообщений поверх других окон"</string>
+    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"Показ элементов интерфейса поверх других окон"</string>
     <string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"Разрешает приложению отображать элементы своего интерфейса поверх окон других программ. Это может мешать вашему взаимодействию с другими приложениями и вести к недоразумениям."</string>
     <string name="permlab_setAnimationScale" msgid="2805103241153907174">"изменять глобальную скорость анимации"</string>
     <string name="permdesc_setAnimationScale" msgid="7690063428924343571">"Приложение сможет в любой момент изменить общую скорость анимации."</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index adfc22a..370c7d6 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -751,7 +751,7 @@
     <string name="relationTypeSister" msgid="1735983554479076481">"Syster"</string>
     <string name="relationTypeSpouse" msgid="394136939428698117">"Make/maka"</string>
     <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Anpassad"</string>
-    <string name="sipAddressTypeHome" msgid="6093598181069359295">"Startsida"</string>
+    <string name="sipAddressTypeHome" msgid="6093598181069359295">"Hem"</string>
     <string name="sipAddressTypeWork" msgid="6920725730797099047">"Arbete"</string>
     <string name="sipAddressTypeOther" msgid="4408436162950119849">"Övrigt"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3037685796058495017">"Ange PIN-kod"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 554c1aa..b952bd4 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1347,8 +1347,8 @@
     <string name="keyboardview_keycode_shift" msgid="2270748814315147690">"Shift"</string>
     <string name="keyboardview_keycode_enter" msgid="2985864015076059467">"Enter"</string>
     <string name="activitychooserview_choose_application" msgid="2125168057199941199">"选择应用"</string>
-    <string name="shareactionprovider_share_with" msgid="806688056141131819">"共享对象"</string>
-    <string name="shareactionprovider_share_with_application" msgid="5627411384638389738">"与“<xliff:g id="APPLICATION_NAME">%s</xliff:g>”共享"</string>
+    <string name="shareactionprovider_share_with" msgid="806688056141131819">"分享方式"</string>
+    <string name="shareactionprovider_share_with_application" msgid="5627411384638389738">"使用<xliff:g id="APPLICATION_NAME">%s</xliff:g>分享"</string>
     <string name="content_description_sliding_handle" msgid="415975056159262248">"滑动手柄。触摸并按住。"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"向上滑动以<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>。"</string>
     <string name="description_direction_down" msgid="5087739728639014595">"向下滑动以<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>。"</string>
@@ -1399,7 +1399,7 @@
     <string name="sha1_fingerprint" msgid="7930330235269404581">"SHA-1 指纹:"</string>
     <string name="activity_chooser_view_see_all" msgid="4292569383976636200">"查看全部"</string>
     <string name="activity_chooser_view_dialog_title_default" msgid="4710013864974040615">"选择活动"</string>
-    <string name="share_action_provider_share_with" msgid="5247684435979149216">"分享对象"</string>
+    <string name="share_action_provider_share_with" msgid="5247684435979149216">"分享方式"</string>
     <string name="status_bar_device_locked" msgid="3092703448690669768">"设备已锁定。"</string>
     <string name="list_delimeter" msgid="3975117572185494152">"、 "</string>
     <string name="sending" msgid="3245653681008218030">"正在发送..."</string>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d899e9d..f1d8c03 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1065,6 +1065,21 @@
         <attr name="maxSdkVersion" format="integer" />
     </declare-styleable>
     
+    <!-- The <code>library</code> tag declares that this apk is providing itself
+         as a shared library for other applications to use.  It can only be used
+         with apks that are built in to the system image.  Other apks can link to
+         it with the {@link #AndroidManifestUsesLibrary uses-library} tag.
+
+         <p>This appears as a child tag of the
+         {@link #AndroidManifestApplication application} tag. -->
+    <declare-styleable name="AndroidManifestLibrary" parent="AndroidManifest">
+        <!-- Required public name of the library, which other components and
+        packages will use when referring to this library.  This is a string using
+        Java-style scoping to ensure it is unique.  The name should typically
+        be the same as the apk's package name. -->
+        <attr name="name" />
+    </declare-styleable>
+
     <!-- The <code>uses-libraries</code> specifies a shared library that this
          package requires to be linked against.  Specifying this flag tells the
          system to include this library's code in your class loader.
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/guide/topics/manifest/intent-filter-element.jd b/docs/html/guide/topics/manifest/intent-filter-element.jd
index f90541c..68da981 100644
--- a/docs/html/guide/topics/manifest/intent-filter-element.jd
+++ b/docs/html/guide/topics/manifest/intent-filter-element.jd
@@ -119,7 +119,11 @@
 
 <p>
 The value must be an integer, such as "{@code 100}".  Higher numbers have a
-higher priority.
+higher priority. The default value is 0.
+The value must be greater than -1000 and less than 1000.</p>
+
+<p>Also see {@link android.content.IntentFilter#setPriority
+setPriority()}.
 </p></dd>
 
 </dl></dd>
diff --git a/docs/html/guide/topics/resources/animation-resource.jd b/docs/html/guide/topics/resources/animation-resource.jd
index 3af52aa..ef64f07 100644
--- a/docs/html/guide/topics/resources/animation-resource.jd
+++ b/docs/html/guide/topics/resources/animation-resource.jd
@@ -217,7 +217,7 @@
     </dd>
 
 <dt id="val-animator-element"><code>&lt;animator&gt;</code></dt>
-    <dd>Animates a over a specified amount of time.
+    <dd>Performs an animation over a specified amount of time.
     Represents a {@link android.animation.ValueAnimator}.
 
       <p class="caps">attributes:</p>
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 8576948..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/docs/html/training/id-auth/authenticate.jd b/docs/html/training/id-auth/authenticate.jd
index c316af7..3084bea 100644
--- a/docs/html/training/id-auth/authenticate.jd
+++ b/docs/html/training/id-auth/authenticate.jd
@@ -114,7 +114,7 @@
     new Handler(new OnError()));    // Callback called if an error occurs
 </pre>
 
-<p>In this example, <code>OnTokenAcquired</code> is a class that extends
+<p>In this example, <code>OnTokenAcquired</code> is a class that implements
 {@link android.accounts.AccountManagerCallback}. {@link android.accounts.AccountManager} calls
 {@link android.accounts.AccountManagerCallback#run run()} on <code>OnTokenAcquired</code> with an
 {@link android.accounts.AccountManagerFuture} that contains a {@link android.os.Bundle}. If
diff --git a/docs/html/training/implementing-navigation/lateral.jd b/docs/html/training/implementing-navigation/lateral.jd
index b59bab2..9a31d7a 100644
--- a/docs/html/training/implementing-navigation/lateral.jd
+++ b/docs/html/training/implementing-navigation/lateral.jd
@@ -131,6 +131,9 @@
     ViewPager mViewPager;
 
     public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_collection_demo);
+
         // ViewPager and its adapters use support library
         // fragments, so use getSupportFragmentManager.
         mDemoCollectionPagerAdapter =
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..fb687cd 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -31,69 +31,34 @@
 // 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 {
+        texture->width = 0;
+        texture->height = 0;
+        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 +66,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 +129,17 @@
     } 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();
+                ALOGW("Path too large to be rendered into a texture");
+                texture->clearTask();
                 texture = NULL;
                 mCache.remove(entry);
             }
@@ -189,6 +153,10 @@
 }
 
 void PathCache::precache(SkPath* path, SkPaint* paint) {
+    if (!Caches::getInstance().tasks.canRunTasks()) {
+        return;
+    }
+
     path = getSourcePath(path);
 
     PathCacheEntry entry(path, paint);
@@ -205,7 +173,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 +185,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..92314b0 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;
@@ -721,7 +717,9 @@
             ALOGD("Shape %s deleted, size = %d", mName, size);
         }
 
-        glDeleteTextures(1, &texture->id);
+        if (texture->id) {
+            glDeleteTextures(1, &texture->id);
+        }
         delete texture;
     }
 }
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/AudioManager.java b/media/java/android/media/AudioManager.java
index 86976b8..135d2c8 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2171,15 +2171,36 @@
     /**
      * @hide
      * Registers a remote control display that will be sent information by remote control clients.
-     * @param rcd
+     * Use this method if your IRemoteControlDisplay is not going to display artwork, otherwise
+     * use {@link #registerRemoteControlDisplay(IRemoteControlDisplay, int, int)} to pass the
+     * artwork size directly, or
+     * {@link #remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay, int, int)} later if artwork
+     * is not yet needed.
+     * @param rcd the IRemoteControlDisplay
      */
     public void registerRemoteControlDisplay(IRemoteControlDisplay rcd) {
+        // passing a negative value for art work width and height as they are unknown at this stage
+        registerRemoteControlDisplay(rcd, /*w*/-1, /*h*/ -1);
+    }
+
+    /**
+     * @hide
+     * Registers a remote control display that will be sent information by remote control clients.
+     * @param rcd
+     * @param w the maximum width of the expected bitmap. Negative values indicate it is
+     *   useless to send artwork.
+     * @param h the maximum height of the expected bitmap. Negative values indicate it is
+     *   useless to send artwork.
+     */
+    public void registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
         if (rcd == null) {
             return;
         }
         IAudioService service = getService();
         try {
-            service.registerRemoteControlDisplay(rcd);
+            // passing a negative value for art work width and height as they are unknown at
+            // this stage
+            service.registerRemoteControlDisplay(rcd, w, h);
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in registerRemoteControlDisplay " + e);
         }
@@ -2223,63 +2244,6 @@
         }
     }
 
-    // FIXME remove because we are not using intents anymore between AudioService and RcDisplay
-    /**
-     * @hide
-     * Broadcast intent action indicating that the displays on the remote controls
-     * should be updated because a new remote control client is now active. If there is no
-     * {@link #EXTRA_REMOTE_CONTROL_CLIENT}, the remote control display should be cleared
-     * because there is no valid client to supply it with information.
-     *
-     * @see #EXTRA_REMOTE_CONTROL_CLIENT
-     */
-    public static final String REMOTE_CONTROL_CLIENT_CHANGED =
-            "android.media.REMOTE_CONTROL_CLIENT_CHANGED";
-
-    // FIXME remove because we are not using intents anymore between AudioService and RcDisplay
-    /**
-     * @hide
-     * The IRemoteControlClientDispatcher monotonically increasing generation counter.
-     *
-     * @see #REMOTE_CONTROL_CLIENT_CHANGED_ACTION
-     */
-    public static final String EXTRA_REMOTE_CONTROL_CLIENT_GENERATION =
-            "android.media.EXTRA_REMOTE_CONTROL_CLIENT_GENERATION";
-
-    // FIXME remove because we are not using intents anymore between AudioService and RcDisplay
-    /**
-     * @hide
-     * The name of the RemoteControlClient.
-     * This String is passed as the client name when calling methods from the
-     * IRemoteControlClientDispatcher interface.
-     *
-     * @see #REMOTE_CONTROL_CLIENT_CHANGED_ACTION
-     */
-    public static final String EXTRA_REMOTE_CONTROL_CLIENT_NAME =
-            "android.media.EXTRA_REMOTE_CONTROL_CLIENT_NAME";
-
-    // FIXME remove because we are not using intents anymore between AudioService and RcDisplay
-    /**
-     * @hide
-     * The media button event receiver associated with the RemoteControlClient.
-     * The {@link android.content.ComponentName} value of the event receiver can be retrieved with
-     * {@link android.content.ComponentName#unflattenFromString(String)}
-     *
-     * @see #REMOTE_CONTROL_CLIENT_CHANGED_ACTION
-     */
-    public static final String EXTRA_REMOTE_CONTROL_EVENT_RECEIVER =
-            "android.media.EXTRA_REMOTE_CONTROL_EVENT_RECEIVER";
-
-    // FIXME remove because we are not using intents anymore between AudioService and RcDisplay
-    /**
-     * @hide
-     * The flags describing what information has changed in the current remote control client.
-     *
-     * @see #REMOTE_CONTROL_CLIENT_CHANGED_ACTION
-     */
-    public static final String EXTRA_REMOTE_CONTROL_CLIENT_INFO_CHANGED =
-            "android.media.EXTRA_REMOTE_CONTROL_CLIENT_INFO_CHANGED";
-
     /**
      *  @hide
      *  Reload audio settings. This method is called by Settings backup
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index ef96fec..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();
         }
@@ -4905,7 +4913,6 @@
                         "  -- vol: " + rcse.mPlaybackVolume +
                         "  -- volMax: " + rcse.mPlaybackVolumeMax +
                         "  -- volObs: " + rcse.mRemoteVolumeObs);
-
             }
         }
         synchronized (mMainRemote) {
@@ -4923,6 +4930,23 @@
 
     /**
      * Helper function:
+     * Display in the log the current entries in the list of remote control displays
+     */
+    private void dumpRCDList(PrintWriter pw) {
+        pw.println("\nRemote Control Display list entries:");
+        synchronized(mRCStack) {
+            final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+            while (displayIterator.hasNext()) {
+                final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+                pw.println("  IRCD: " + di.mRcDisplay +
+                        "  -- w:" + di.mArtworkExpectedWidth +
+                        "  -- h:" + di.mArtworkExpectedHeight);
+            }
+        }
+    }
+
+    /**
+     * Helper function:
      * Remove any entry in the remote control stack that has the same package name as packageName
      * Pre-condition: packageName != null
      */
@@ -5059,16 +5083,20 @@
      */
     private void setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration,
             PendingIntent newMediaIntent, boolean clearing) {
-        // NOTE: Only one IRemoteControlDisplay supported in this implementation
-        if (mRcDisplay != null) {
-            try {
-                mRcDisplay.setCurrentClientId(
-                        newClientGeneration, newMediaIntent, clearing);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc() "+e);
-                // if we had a display before, stop monitoring its death
-                rcDisplay_stopDeathMonitor_syncRcStack();
-                mRcDisplay = null;
+        synchronized(mRCStack) {
+            if (mRcDisplays.size() > 0) {
+                final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+                while (displayIterator.hasNext()) {
+                    final DisplayInfoForServer di = displayIterator.next();
+                    try {
+                        di.mRcDisplay.setCurrentClientId(
+                                newClientGeneration, newMediaIntent, clearing);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc()",e);
+                        di.release();
+                        displayIterator.remove();
+                    }
+                }
             }
         }
     }
@@ -5086,7 +5114,7 @@
                 try {
                     se.mRcClient.setCurrentClientGenerationId(newClientGeneration);
                 } catch (RemoteException e) {
-                    Log.w(TAG, "Dead client in setNewRcClientGenerationOnClients_syncRcsCurrc()"+e);
+                    Log.w(TAG, "Dead client in setNewRcClientGenerationOnClients_syncRcsCurrc()",e);
                     stackIterator.remove();
                     se.unlinkToRcClientDeath();
                 }
@@ -5144,8 +5172,7 @@
 
                     // tell the current client that it needs to send info
                     try {
-                        mCurrentRcClient.onInformationRequested(mCurrentRcClientGen,
-                                flags, mArtworkExpectedWidth, mArtworkExpectedHeight);
+                        mCurrentRcClient.onInformationRequested(mCurrentRcClientGen, flags);
                     } catch (RemoteException e) {
                         Log.e(TAG, "Current valid remote client is dead: "+e);
                         mCurrentRcClient = null;
@@ -5409,13 +5436,9 @@
                             rccId = rcse.mRccId;
 
                             // there is a new (non-null) client:
-                            // 1/ give the new client the current display (if any)
-                            if (mRcDisplay != null) {
-                                try {
-                                    rcse.mRcClient.plugRemoteControlDisplay(mRcDisplay);
-                                } catch (RemoteException e) {
-                                    Log.e(TAG, "Error connecting RCD to RCC in RCC registration",e);
-                                }
+                            // 1/ give the new client the displays (if any)
+                            if (mRcDisplays.size() > 0) {
+                                plugRemoteControlDisplaysIntoClient_syncRcStack(rcse.mRcClient);
                             }
                             // 2/ monitor the new client's death
                             IBinder b = rcse.mRcClient.asBinder();
@@ -5485,102 +5508,141 @@
         }
     }
 
-    /**
-     * The remote control displays.
-     * Access synchronized on mRCStack
-     * NOTE: Only one IRemoteControlDisplay supported in this implementation
-     */
-    private IRemoteControlDisplay mRcDisplay;
-    private RcDisplayDeathHandler mRcDisplayDeathHandler;
-    private int mArtworkExpectedWidth = -1;
-    private int mArtworkExpectedHeight = -1;
-    /**
-     * Inner class to monitor remote control display deaths, and unregister them from the list
-     * of displays if necessary.
-     */
-    private class RcDisplayDeathHandler implements IBinder.DeathRecipient {
-        private IBinder mCb; // To be notified of client's death
 
-        public RcDisplayDeathHandler(IBinder b) {
-            if (DEBUG_RC) Log.i(TAG, "new RcDisplayDeathHandler for "+b);
-            mCb = b;
+    /**
+     * A class to encapsulate all the information about a remote control display.
+     * After instanciation, init() must always be called before the object is added in the list
+     * of displays.
+     * Before being removed from the list of displays, release() must always be called (otherwise
+     * it will leak death handlers).
+     */
+    private class DisplayInfoForServer implements IBinder.DeathRecipient {
+        /** may never be null */
+        private IRemoteControlDisplay mRcDisplay;
+        private IBinder mRcDisplayBinder;
+        private int mArtworkExpectedWidth = -1;
+        private int mArtworkExpectedHeight = -1;
+
+        public DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h) {
+            if (DEBUG_RC) Log.i(TAG, "new DisplayInfoForServer for " + rcd + " w=" + w + " h=" + h);
+            mRcDisplay = rcd;
+            mRcDisplayBinder = rcd.asBinder();
+            mArtworkExpectedWidth = w;
+            mArtworkExpectedHeight = h;
+        }
+
+        public boolean init() {
+            try {
+                mRcDisplayBinder.linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                // remote control display is DOA, disqualify it
+                Log.w(TAG, "registerRemoteControlDisplay() has a dead client " + mRcDisplayBinder);
+                return false;
+            }
+            return true;
+        }
+
+        public void release() {
+            try {
+                mRcDisplayBinder.unlinkToDeath(this, 0);
+            } catch (java.util.NoSuchElementException e) {
+                // not much we can do here, the display should have been unregistered anyway
+                Log.e(TAG, "Error in DisplaInfoForServer.relase()", e);
+            }
         }
 
         public void binderDied() {
             synchronized(mRCStack) {
-                Log.w(TAG, "RemoteControl: display died");
-                mRcDisplay = null;
+                Log.w(TAG, "RemoteControl: display " + mRcDisplay + " died");
+                // remove the display from the list
+                final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+                while (displayIterator.hasNext()) {
+                    final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+                    if (di.mRcDisplay == mRcDisplay) {
+                        if (DEBUG_RC) Log.w(TAG, " RCD removed from list");
+                        displayIterator.remove();
+                        return;
+                    }
+                }
             }
         }
-
-        public void unlinkToRcDisplayDeath() {
-            if (DEBUG_RC) Log.i(TAG, "unlinkToRcDisplayDeath for "+mCb);
-            try {
-                mCb.unlinkToDeath(this, 0);
-            } catch (java.util.NoSuchElementException e) {
-                // not much we can do here, the display was being unregistered anyway
-                Log.e(TAG, "Encountered " + e + " in unlinkToRcDisplayDeath()");
-                e.printStackTrace();
-            }
-        }
-
-    }
-
-    private void rcDisplay_stopDeathMonitor_syncRcStack() {
-        if (mRcDisplay != null) { // implies (mRcDisplayDeathHandler != null)
-            // we had a display before, stop monitoring its death
-            mRcDisplayDeathHandler.unlinkToRcDisplayDeath();
-        }
     }
 
-    private void rcDisplay_startDeathMonitor_syncRcStack() {
-        if (mRcDisplay != null) {
-            // new non-null display, monitor its death
-            IBinder b = mRcDisplay.asBinder();
-            mRcDisplayDeathHandler = new RcDisplayDeathHandler(b);
+    /**
+     * The remote control displays.
+     * Access synchronized on mRCStack
+     */
+    private ArrayList<DisplayInfoForServer> mRcDisplays = new ArrayList<DisplayInfoForServer>(1);
+
+    /**
+     * Plug each registered display into the specified client
+     * @param rcc, guaranteed non null
+     */
+    private void plugRemoteControlDisplaysIntoClient_syncRcStack(IRemoteControlClient rcc) {
+        final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+        while (displayIterator.hasNext()) {
+            final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
             try {
-                b.linkToDeath(mRcDisplayDeathHandler, 0);
+                rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth,
+                        di.mArtworkExpectedHeight);
             } catch (RemoteException e) {
-                // remote control display is DOA, disqualify it
-                Log.w(TAG, "registerRemoteControlDisplay() has a dead client " + b);
-                mRcDisplay = null;
+                Log.e(TAG, "Error connecting RCD to RCC in RCC registration",e);
             }
         }
     }
 
     /**
+     * Is the remote control display interface already registered
+     * @param rcd
+     * @return true if the IRemoteControlDisplay is already in the list of displays
+     */
+    private boolean rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd) {
+        final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+        while (displayIterator.hasNext()) {
+            final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+            if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Register an IRemoteControlDisplay.
      * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient
      * at the top of the stack to update the new display with its information.
-     * Since only one IRemoteControlDisplay is supported, this will unregister the previous display.
+     * @see android.media.IAudioService#registerRemoteControlDisplay(android.media.IRemoteControlDisplay, int, int)
      * @param rcd the IRemoteControlDisplay to register. No effect if null.
+     * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
+     *   display doesn't need to receive artwork.
+     * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
+     *   display doesn't need to receive artwork.
      */
-    public void registerRemoteControlDisplay(IRemoteControlDisplay rcd) {
+    public void registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
         if (DEBUG_RC) Log.d(TAG, ">>> registerRemoteControlDisplay("+rcd+")");
         synchronized(mAudioFocusLock) {
             synchronized(mRCStack) {
-                if ((mRcDisplay == rcd) || (rcd == null)) {
+                if ((rcd == null) || rcDisplayIsPluggedIn_syncRcStack(rcd)) {
                     return;
                 }
-                // if we had a display before, stop monitoring its death
-                rcDisplay_stopDeathMonitor_syncRcStack();
-                mRcDisplay = rcd;
-                // new display, start monitoring its death
-                rcDisplay_startDeathMonitor_syncRcStack();
+                DisplayInfoForServer di = new DisplayInfoForServer(rcd, w, h);
+                if (!di.init()) {
+                    if (DEBUG_RC) Log.e(TAG, " error registering RCD");
+                    return;
+                }
+                // add RCD to list of displays
+                mRcDisplays.add(di);
 
-                // let all the remote control clients know there is a new display, so the remote
-                //   control stack traversal order doesn't matter.
-                // No need to unplug the previous because we only support one display
-                // and the clients don't track the death of the display
+                // let all the remote control clients know there is a new display (so the remote
+                //   control stack traversal order doesn't matter).
                 Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
                 while(stackIterator.hasNext()) {
                     RemoteControlStackEntry rcse = stackIterator.next();
                     if(rcse.mRcClient != null) {
                         try {
-                            rcse.mRcClient.plugRemoteControlDisplay(mRcDisplay);
+                            rcse.mRcClient.plugRemoteControlDisplay(rcd, w, h);
                         } catch (RemoteException e) {
-                            Log.e(TAG, "Error connecting remote control display to client: " + e);
-                            e.printStackTrace();
+                            Log.e(TAG, "Error connecting RCD to client: ", e);
                         }
                     }
                 }
@@ -5593,44 +5655,86 @@
 
     /**
      * Unregister an IRemoteControlDisplay.
-     * Since only one IRemoteControlDisplay is supported, this has no effect if the one to
-     *    unregister is not the current one.
+     * No effect if the IRemoteControlDisplay hasn't been successfully registered.
+     * @see android.media.IAudioService#unregisterRemoteControlDisplay(android.media.IRemoteControlDisplay)
      * @param rcd the IRemoteControlDisplay to unregister. No effect if null.
      */
     public void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
         if (DEBUG_RC) Log.d(TAG, "<<< unregisterRemoteControlDisplay("+rcd+")");
         synchronized(mRCStack) {
-            // only one display here, so you can only unregister the current display
-            if ((rcd == null) || (rcd != mRcDisplay)) {
-                if (DEBUG_RC) Log.w(TAG, "    trying to unregister unregistered RCD");
+            if (rcd == null) {
                 return;
             }
-            // if we had a display before, stop monitoring its death
-            rcDisplay_stopDeathMonitor_syncRcStack();
-            mRcDisplay = null;
 
-            // disconnect this remote control display from all the clients, so the remote
-            //   control stack traversal order doesn't matter
-            Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
-            while(stackIterator.hasNext()) {
-                RemoteControlStackEntry rcse = stackIterator.next();
-                if(rcse.mRcClient != null) {
-                    try {
-                        rcse.mRcClient.unplugRemoteControlDisplay(rcd);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error disconnecting remote control display to client: " + e);
-                        e.printStackTrace();
+            boolean displayWasPluggedIn = false;
+            final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+            while (displayIterator.hasNext() && !displayWasPluggedIn) {
+                final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+                if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
+                    displayWasPluggedIn = true;
+                    di.release();
+                    displayIterator.remove();
+                }
+            }
+
+            if (displayWasPluggedIn) {
+                // disconnect this remote control display from all the clients, so the remote
+                //   control stack traversal order doesn't matter
+                final Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+                while(stackIterator.hasNext()) {
+                    final RemoteControlStackEntry rcse = stackIterator.next();
+                    if(rcse.mRcClient != null) {
+                        try {
+                            rcse.mRcClient.unplugRemoteControlDisplay(rcd);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Error disconnecting remote control display to client: ", e);
+                        }
                     }
                 }
+            } else {
+                if (DEBUG_RC) Log.w(TAG, "  trying to unregister unregistered RCD");
             }
         }
     }
 
+    /**
+     * Update the size of the artwork used by an IRemoteControlDisplay.
+     * @see android.media.IAudioService#remoteControlDisplayUsesBitmapSize(android.media.IRemoteControlDisplay, int, int)
+     * @param rcd the IRemoteControlDisplay with the new artwork size requirement
+     * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
+     *   display doesn't need to receive artwork.
+     * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
+     *   display doesn't need to receive artwork.
+     */
     public void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) {
         synchronized(mRCStack) {
-            // NOTE: Only one IRemoteControlDisplay supported in this implementation
-            mArtworkExpectedWidth = w;
-            mArtworkExpectedHeight = h;
+            final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+            boolean artworkSizeUpdate = false;
+            while (displayIterator.hasNext() && !artworkSizeUpdate) {
+                final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
+                if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
+                    if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) {
+                        di.mArtworkExpectedWidth = w;
+                        di.mArtworkExpectedHeight = h;
+                        artworkSizeUpdate = true;
+                    }
+                }
+            }
+            if (artworkSizeUpdate) {
+                // RCD is currently plugged in and its artwork size has changed, notify all RCCs,
+                // stack traversal order doesn't matter
+                final Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+                while(stackIterator.hasNext()) {
+                    final RemoteControlStackEntry rcse = stackIterator.next();
+                    if(rcse.mRcClient != null) {
+                        try {
+                            rcse.mRcClient.setBitmapSizeForDisplay(rcd, w, h);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Error setting bitmap size for RCD on RCC: ", e);
+                        }
+                    }
+                }
+            }
         }
     }
 
@@ -6233,6 +6337,7 @@
         dumpFocusStack(pw);
         dumpRCStack(pw);
         dumpRCCStack(pw);
+        dumpRCDList(pw);
         dumpStreamStates(pw);
         dumpRingerMode(pw);
         pw.println("\nAudio routes:");
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index ea99069..312c252 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -131,8 +131,31 @@
     oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent,
            in IRemoteControlClient rcClient);
 
-    oneway void   registerRemoteControlDisplay(in IRemoteControlDisplay rcd);
+    /**
+     * Register an IRemoteControlDisplay.
+     * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient
+     * at the top of the stack to update the new display with its information.
+     * @param rcd the IRemoteControlDisplay to register. No effect if null.
+     * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
+     *   display doesn't need to receive artwork.
+     * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
+     *   display doesn't need to receive artwork.
+     */
+    oneway void   registerRemoteControlDisplay(in IRemoteControlDisplay rcd, int w, int h);
+    /**
+     * Unregister an IRemoteControlDisplay.
+     * No effect if the IRemoteControlDisplay hasn't been successfully registered.
+     * @param rcd the IRemoteControlDisplay to unregister. No effect if null.
+     */
     oneway void unregisterRemoteControlDisplay(in IRemoteControlDisplay rcd);
+    /**
+     * Update the size of the artwork used by an IRemoteControlDisplay.
+     * @param rcd the IRemoteControlDisplay with the new artwork size requirement
+     * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
+     *   display doesn't need to receive artwork.
+     * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
+     *   display doesn't need to receive artwork.
+     */
     oneway void remoteControlDisplayUsesBitmapSize(in IRemoteControlDisplay rcd, int w, int h);
 
     oneway void setPlaybackInfoForRcc(int rccId, int what, int value);
diff --git a/media/java/android/media/IRemoteControlClient.aidl b/media/java/android/media/IRemoteControlClient.aidl
index 0fbba20..5600263 100644
--- a/media/java/android/media/IRemoteControlClient.aidl
+++ b/media/java/android/media/IRemoteControlClient.aidl
@@ -34,18 +34,17 @@
      *   parameters are valid.
      * @param generationId
      * @param infoFlags
-     * @param artWidth if > 0, artHeight must be > 0 too.
-     * @param artHeight
      * FIXME: is infoFlags required? since the RCC pushes info, this might always be called
      *        with RC_INFO_ALL
      */
-    void onInformationRequested(int generationId, int infoFlags, int artWidth, int artHeight);
+    void onInformationRequested(int generationId, int infoFlags);
 
     /**
      * Sets the generation counter of the current client that is displayed on the remote control.
      */
     void setCurrentClientGenerationId(int clientGeneration);
 
-    void   plugRemoteControlDisplay(IRemoteControlDisplay rcd);
+    void   plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h);
     void unplugRemoteControlDisplay(IRemoteControlDisplay rcd);
+    void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h);
 }
\ No newline at end of file
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index 4c71ace..9a0ecdf 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -36,6 +36,8 @@
 import android.util.Log;
 
 import java.lang.IllegalArgumentException;
+import java.util.ArrayList;
+import java.util.Iterator;
 
 /**
  * RemoteControlClient enables exposing information meant to be consumed by remote controls
@@ -498,13 +500,7 @@
             if (key != BITMAP_KEY_ARTWORK) {
                 throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key));
             }
-            if ((mArtworkExpectedWidth > 0) && (mArtworkExpectedHeight > 0)) {
-                mEditorArtwork = scaleBitmapIfTooBig(bitmap,
-                        mArtworkExpectedWidth, mArtworkExpectedHeight);
-            } else {
-                // no valid resize dimensions, store as is
-                mEditorArtwork = bitmap;
-            }
+            mEditorArtwork = bitmap;
             mArtworkChanged = true;
             return this;
         }
@@ -536,10 +532,10 @@
             synchronized(mCacheLock) {
                 // assign the edited data
                 mMetadata = new Bundle(mEditorMetadata);
-                if ((mArtwork != null) && (!mArtwork.equals(mEditorArtwork))) {
-                    mArtwork.recycle();
+                if ((mOriginalArtwork != null) && (!mOriginalArtwork.equals(mEditorArtwork))) {
+                    mOriginalArtwork.recycle();
                 }
-                mArtwork = mEditorArtwork;
+                mOriginalArtwork = mEditorArtwork;
                 mEditorArtwork = null;
                 if (mMetadataChanged & mArtworkChanged) {
                     // send to remote control display if conditions are met
@@ -571,7 +567,7 @@
             editor.mArtworkChanged = true;
         } else {
             editor.mEditorMetadata = new Bundle(mMetadata);
-            editor.mEditorArtwork = mArtwork;
+            editor.mEditorArtwork = mOriginalArtwork;
             editor.mMetadataChanged = false;
             editor.mArtworkChanged = false;
         }
@@ -766,11 +762,7 @@
      * accessed to be resized, in which case a copy will be made. This would add overhead in
      * Bundle operations.
      */
-    private Bitmap mArtwork;
-    private final int ARTWORK_DEFAULT_SIZE = 256;
-    private final int ARTWORK_INVALID_SIZE = -1;
-    private int mArtworkExpectedWidth = ARTWORK_DEFAULT_SIZE;
-    private int mArtworkExpectedHeight = ARTWORK_DEFAULT_SIZE;
+    private Bitmap mOriginalArtwork;
     /**
      * Cache for the transport control mask.
      * Access synchronized on mCacheLock
@@ -802,10 +794,27 @@
     private final PendingIntent mRcMediaIntent;
 
     /**
-     * The remote control display to which this client will send information.
-     * NOTE: Only one IRemoteControlDisplay supported in this implementation
+     * A class to encapsulate all the information about a remote control display.
+     * A RemoteControlClient's metadata and state may be displayed on multiple IRemoteControlDisplay
      */
-    private IRemoteControlDisplay mRcDisplay;
+    private class DisplayInfoForClient {
+        /** may never be null */
+        private IRemoteControlDisplay mRcDisplay;
+        private int mArtworkExpectedWidth;
+        private int mArtworkExpectedHeight;
+
+        DisplayInfoForClient(IRemoteControlDisplay rcd, int w, int h) {
+            mRcDisplay = rcd;
+            mArtworkExpectedWidth = w;
+            mArtworkExpectedHeight = h;
+        }
+    }
+
+    /**
+     * The list of remote control displays to which this client will send information.
+     * Accessed and modified synchronized on mCacheLock
+     */
+    private ArrayList<DisplayInfoForClient> mRcDisplays = new ArrayList<DisplayInfoForClient>(1);
 
     /**
      * @hide
@@ -827,17 +836,14 @@
      */
     private final IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() {
 
-        public void onInformationRequested(int clientGeneration, int infoFlags,
-                int artWidth, int artHeight) {
+        public void onInformationRequested(int clientGeneration, int infoFlags) {
             // only post messages, we can't block here
             if (mEventHandler != null) {
                 // signal new client
                 mEventHandler.removeMessages(MSG_NEW_INTERNAL_CLIENT_GEN);
                 mEventHandler.dispatchMessage(
-                        mEventHandler.obtainMessage(
-                                MSG_NEW_INTERNAL_CLIENT_GEN,
-                                artWidth, artHeight,
-                                new Integer(clientGeneration)));
+                        mEventHandler.obtainMessage(MSG_NEW_INTERNAL_CLIENT_GEN,
+                                /*arg1*/ clientGeneration, /*arg2, ignored*/ 0));
                 // send the information
                 mEventHandler.removeMessages(MSG_REQUEST_PLAYBACK_STATE);
                 mEventHandler.removeMessages(MSG_REQUEST_METADATA);
@@ -861,21 +867,29 @@
             }
         }
 
-        public void plugRemoteControlDisplay(IRemoteControlDisplay rcd) {
+        public void plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
             // only post messages, we can't block here
-            if (mEventHandler != null) {
+            if ((mEventHandler != null) && (rcd != null)) {
                 mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
-                        MSG_PLUG_DISPLAY, rcd));
+                        MSG_PLUG_DISPLAY, w, h, rcd));
             }
         }
 
         public void unplugRemoteControlDisplay(IRemoteControlDisplay rcd) {
             // only post messages, we can't block here
-            if (mEventHandler != null) {
+            if ((mEventHandler != null) && (rcd != null)) {
                 mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
                         MSG_UNPLUG_DISPLAY, rcd));
             }
         }
+
+        public void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h) {
+            // only post messages, we can't block here
+            if ((mEventHandler != null) && (rcd != null)) {
+                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
+                        MSG_UPDATE_DISPLAY_ARTWORK_SIZE, w, h, rcd));
+            }
+        }
     };
 
     /**
@@ -915,6 +929,7 @@
     private final static int MSG_NEW_CURRENT_CLIENT_GEN = 6;
     private final static int MSG_PLUG_DISPLAY = 7;
     private final static int MSG_UNPLUG_DISPLAY = 8;
+    private final static int MSG_UPDATE_DISPLAY_ARTWORK_SIZE = 9;
 
     private class EventHandler extends Handler {
         public EventHandler(RemoteControlClient rcc, Looper looper) {
@@ -945,17 +960,20 @@
                     }
                     break;
                 case MSG_NEW_INTERNAL_CLIENT_GEN:
-                    onNewInternalClientGen((Integer)msg.obj, msg.arg1, msg.arg2);
+                    onNewInternalClientGen(msg.arg1);
                     break;
                 case MSG_NEW_CURRENT_CLIENT_GEN:
                     onNewCurrentClientGen(msg.arg1);
                     break;
                 case MSG_PLUG_DISPLAY:
-                    onPlugDisplay((IRemoteControlDisplay)msg.obj);
+                    onPlugDisplay((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2);
                     break;
                 case MSG_UNPLUG_DISPLAY:
                     onUnplugDisplay((IRemoteControlDisplay)msg.obj);
                     break;
+                case MSG_UPDATE_DISPLAY_ARTWORK_SIZE:
+                    onUpdateDisplayArtworkSize((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2);
+                    break;
                 default:
                     Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler");
             }
@@ -963,75 +981,106 @@
     }
 
     //===========================================================
-    // Communication with IRemoteControlDisplay
-
-    private void detachFromDisplay_syncCacheLock() {
-        mRcDisplay = null;
-        mArtworkExpectedWidth = ARTWORK_INVALID_SIZE;
-        mArtworkExpectedHeight = ARTWORK_INVALID_SIZE;
-    }
+    // Communication with the IRemoteControlDisplay (the displays known to the system)
 
     private void sendPlaybackState_syncCacheLock() {
-        if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
-            try {
-                mRcDisplay.setPlaybackState(mInternalClientGenId, mPlaybackState,
-                        mPlaybackStateChangeTimeMs);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error in setPlaybackState(), dead display "+e);
-                detachFromDisplay_syncCacheLock();
+        if (mCurrentClientGenId == mInternalClientGenId) {
+            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
+            while (displayIterator.hasNext()) {
+                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+                try {
+                    di.mRcDisplay.setPlaybackState(mInternalClientGenId,
+                            mPlaybackState, mPlaybackStateChangeTimeMs);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Error in setPlaybackState(), dead display " + di.mRcDisplay, e);
+                    displayIterator.remove();
+                }
             }
         }
     }
 
     private void sendMetadata_syncCacheLock() {
-        if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
-            try {
-                mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error in sendPlaybackState(), dead display "+e);
-                detachFromDisplay_syncCacheLock();
+        if (mCurrentClientGenId == mInternalClientGenId) {
+            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
+            while (displayIterator.hasNext()) {
+                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+                try {
+                    di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Error in setMetadata(), dead display " + di.mRcDisplay, e);
+                    displayIterator.remove();
+                }
             }
         }
     }
 
     private void sendTransportControlFlags_syncCacheLock() {
-        if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
-            try {
-                mRcDisplay.setTransportControlFlags(mInternalClientGenId,
-                        mTransportControlFlags);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error in sendTransportControlFlags(), dead display "+e);
-                detachFromDisplay_syncCacheLock();
+        if (mCurrentClientGenId == mInternalClientGenId) {
+            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
+            while (displayIterator.hasNext()) {
+                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+                try {
+                    di.mRcDisplay.setTransportControlFlags(mInternalClientGenId,
+                            mTransportControlFlags);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Error in setTransportControlFlags(), dead display " + di.mRcDisplay,
+                            e);
+                    displayIterator.remove();
+                }
             }
         }
     }
 
     private void sendArtwork_syncCacheLock() {
-        if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
-            // even though we have already scaled in setArtwork(), when this client needs to
-            // send the bitmap, there might be newer and smaller expected dimensions, so we have
-            // to check again.
-            mArtwork = scaleBitmapIfTooBig(mArtwork, mArtworkExpectedWidth, mArtworkExpectedHeight);
-            try {
-                mRcDisplay.setArtwork(mInternalClientGenId, mArtwork);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error in sendArtwork(), dead display "+e);
-                detachFromDisplay_syncCacheLock();
+        // FIXME modify to cache all requested sizes?
+        if (mCurrentClientGenId == mInternalClientGenId) {
+            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
+            while (displayIterator.hasNext()) {
+                if (!sendArtworkToDisplay((DisplayInfoForClient) displayIterator.next())) {
+                    displayIterator.remove();
+                }
             }
         }
     }
 
-    private void sendMetadataWithArtwork_syncCacheLock() {
-        if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
-            // even though we have already scaled in setArtwork(), when this client needs to
-            // send the bitmap, there might be newer and smaller expected dimensions, so we have
-            // to check again.
-            mArtwork = scaleBitmapIfTooBig(mArtwork, mArtworkExpectedWidth, mArtworkExpectedHeight);
+    /**
+     * Send artwork to an IRemoteControlDisplay.
+     * @param di encapsulates the IRemoteControlDisplay that will receive the artwork, and its
+     *    dimension requirements.
+     * @return false if there was an error communicating with the IRemoteControlDisplay.
+     */
+    private boolean sendArtworkToDisplay(DisplayInfoForClient di) {
+        if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) {
+            Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork,
+                    di.mArtworkExpectedWidth, di.mArtworkExpectedHeight);
             try {
-                mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, mArtwork);
+                di.mRcDisplay.setArtwork(mInternalClientGenId, artwork);
             } catch (RemoteException e) {
-                Log.e(TAG, "Error in setAllMetadata(), dead display "+e);
-                detachFromDisplay_syncCacheLock();
+                Log.e(TAG, "Error in sendArtworkToDisplay(), dead display " + di.mRcDisplay, e);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void sendMetadataWithArtwork_syncCacheLock() {
+        // FIXME modify to cache all requested sizes?
+        if (mCurrentClientGenId == mInternalClientGenId) {
+            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
+            while (displayIterator.hasNext()) {
+                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+                try {
+                    if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) {
+                        Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork,
+                                di.mArtworkExpectedWidth, di.mArtworkExpectedHeight);
+                        di.mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, artwork);
+                    } else {
+                        di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
+                    }
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Error when setting metadata, dead display " + di.mRcDisplay, e);
+                    displayIterator.remove();
+                }
             }
         }
     }
@@ -1067,15 +1116,11 @@
     //===========================================================
     // Message handlers
 
-    private void onNewInternalClientGen(Integer clientGeneration, int artWidth, int artHeight) {
+    private void onNewInternalClientGen(int clientGeneration) {
         synchronized (mCacheLock) {
             // this remote control client is told it is the "focused" one:
             // it implies that now (mCurrentClientGenId == mInternalClientGenId) is true
-            mInternalClientGenId = clientGeneration.intValue();
-            if (artWidth > 0) {
-                mArtworkExpectedWidth = artWidth;
-                mArtworkExpectedHeight = artHeight;
-            }
+            mInternalClientGenId = clientGeneration;
         }
     }
 
@@ -1085,18 +1130,62 @@
         }
     }
 
-    private void onPlugDisplay(IRemoteControlDisplay rcd) {
+    /** pre-condition rcd != null */
+    private void onPlugDisplay(IRemoteControlDisplay rcd, int w, int h) {
         synchronized(mCacheLock) {
-            mRcDisplay = rcd;
+            // do we have this display already?
+            boolean displayKnown = false;
+            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
+            while (displayIterator.hasNext() && !displayKnown) {
+                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+                displayKnown = di.mRcDisplay.asBinder().equals(rcd.asBinder());
+                if (displayKnown) {
+                    // this display was known but the change in artwork size will cause the
+                    // artwork to be refreshed
+                    if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) {
+                        di.mArtworkExpectedWidth = w;
+                        di.mArtworkExpectedHeight = h;
+                        if (!sendArtworkToDisplay(di)) {
+                            displayIterator.remove();
+                        }
+                    }
+                }
+            }
+            if (!displayKnown) {
+                mRcDisplays.add(new DisplayInfoForClient(rcd, w, h));
+            }
         }
     }
 
+    /** pre-condition rcd != null */
     private void onUnplugDisplay(IRemoteControlDisplay rcd) {
         synchronized(mCacheLock) {
-            if ((mRcDisplay != null) && (mRcDisplay.asBinder().equals(rcd.asBinder()))) {
-                mRcDisplay = null;
-                mArtworkExpectedWidth = ARTWORK_DEFAULT_SIZE;
-                mArtworkExpectedHeight = ARTWORK_DEFAULT_SIZE;
+            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
+            while (displayIterator.hasNext()) {
+                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+                if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
+                    displayIterator.remove();
+                    return;
+                }
+            }
+        }
+    }
+
+    /** pre-condition rcd != null */
+    private void onUpdateDisplayArtworkSize(IRemoteControlDisplay rcd, int w, int h) {
+        synchronized(mCacheLock) {
+            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
+            while (displayIterator.hasNext()) {
+                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+                if (di.mRcDisplay.asBinder().equals(rcd.asBinder()) &&
+                        ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h))) {
+                    di.mArtworkExpectedWidth = w;
+                    di.mArtworkExpectedHeight = h;
+                    if (!sendArtworkToDisplay(di)) {
+                        displayIterator.remove();
+                    }
+                    break;
+                }
             }
         }
     }
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/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java
index ffa88d5..fda47d5 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java
@@ -250,6 +250,15 @@
     }
 
     @Override
+    protected void onSizeChanged (int w, int h, int oldw, int oldh) {
+        if (mAttached) {
+            int dim = Math.min(512, Math.max(w, h));
+            if (DEBUG) Log.v(TAG, "TCV uses bitmap size=" + dim);
+            mAudioManager.remoteControlDisplayUsesBitmapSize(mIRCD, dim, dim);
+        }
+    }
+
+    @Override
     public void onDetachedFromWindow() {
         if (DEBUG) Log.v(TAG, "onDetachFromWindow()");
         super.onDetachedFromWindow();
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
index 8562f0c..30c95fb 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
@@ -200,6 +200,9 @@
                     stretch, stretch, type, flags, PixelFormat.TRANSLUCENT);
             lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
             lp.windowAnimations = com.android.internal.R.style.Animation_LockScreen;
+            lp.screenOrientation = enableScreenRotation ?
+                    ActivityInfo.SCREEN_ORIENTATION_USER : ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+
             if (ActivityManager.isHighEndGfx()) {
                 lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
                 lp.privateFlags |=
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 6ba5cff..d0048bf 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -1243,7 +1243,11 @@
                     unbindCurrentMethodLocked(false, false);
                     return;
                 }
-                if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
+                // 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 (true || DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
                 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
                         MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
                 if (mCurClient != null) {
@@ -1689,7 +1693,7 @@
                     }
                 }
 
-                if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
+                if (true || DEBUG) Slog.v(TAG, "Client requesting input be shown");
                 return showCurrentInputLocked(flags, resultReceiver);
             }
         } finally {
@@ -1713,6 +1717,8 @@
 
         boolean res = false;
         if (mCurMethod != null) {
+            if (true ||DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken,
+                    new RuntimeException("here").fillInStackTrace());
             executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
                     MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
                     resultReceiver));
@@ -1784,12 +1790,12 @@
     boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
         if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
                 && (mShowExplicitlyRequested || mShowForced)) {
-            if (DEBUG) Slog.v(TAG,
+            if (true ||DEBUG) Slog.v(TAG,
                     "Not hiding: explicit show not cancelled by non-explicit hide");
             return false;
         }
         if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
-            if (DEBUG) Slog.v(TAG,
+            if (true ||DEBUG) Slog.v(TAG,
                     "Not hiding: forced show not cancelled by not-always hide");
             return false;
         }
@@ -2301,6 +2307,8 @@
             case MSG_SHOW_SOFT_INPUT:
                 args = (SomeArgs)msg.obj;
                 try {
+                    if (true || DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
+                            + msg.arg1 + ", " + args.arg2 + ")");
                     ((IInputMethod)args.arg1).showSoftInput(msg.arg1,
                             (ResultReceiver)args.arg2);
                 } catch (RemoteException e) {
@@ -2310,6 +2318,8 @@
             case MSG_HIDE_SOFT_INPUT:
                 args = (SomeArgs)msg.obj;
                 try {
+                    if (true || DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
+                            + args.arg2 + ")");
                     ((IInputMethod)args.arg1).hideSoftInput(0,
                             (ResultReceiver)args.arg2);
                 } catch (RemoteException e) {
@@ -2319,7 +2329,7 @@
             case MSG_ATTACH_TOKEN:
                 args = (SomeArgs)msg.obj;
                 try {
-                    if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
+                    if (true || DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
                     ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
                 } catch (RemoteException e) {
                 }
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 1e394d6..9e036d1 100644
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -1255,9 +1255,6 @@
                     sendAccessibilityEvent(notification, pkg);
                 }
 
-                // finally, keep some of this information around for later use
-                mArchive.record(n);
-
                 notifyPostedLocked(r);
             } else {
                 Slog.e(TAG, "Ignoring notification with icon==0: " + notification);
@@ -1472,6 +1469,9 @@
         if (mLedNotification == r) {
             mLedNotification = null;
         }
+
+        // Save it for users of getHistoricalNotifications()
+        mArchive.record(r.sbn);
     }
 
     /**
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 51f001f..2d12a77 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -170,7 +170,7 @@
 public class PackageManagerService extends IPackageManager.Stub {
     static final String TAG = "PackageManager";
     static final boolean DEBUG_SETTINGS = false;
-    static final boolean DEBUG_PREFERRED = true;
+    static final boolean DEBUG_PREFERRED = false;
     static final boolean DEBUG_UPGRADE = false;
     private static final boolean DEBUG_INSTALL = false;
     private static final boolean DEBUG_REMOVE = false;
@@ -339,9 +339,20 @@
     final SparseArray<HashSet<String>> mSystemPermissions =
             new SparseArray<HashSet<String>>();
 
+    static final class SharedLibraryEntry {
+        final String path;
+        final String apk;
+
+        SharedLibraryEntry(String _path, String _apk) {
+            path = _path;
+            apk = _apk;
+        }
+    }
+
     // These are the built-in shared libraries that were read from the
     // etc/permissions.xml file.
-    final HashMap<String, String> mSharedLibraries = new HashMap<String, String>();
+    final HashMap<String, SharedLibraryEntry> mSharedLibraries
+            = new HashMap<String, SharedLibraryEntry>();
 
     // Temporary for building the final shared libraries for an .apk.
     String[] mTmpSharedLibraries = null;
@@ -390,8 +401,7 @@
     final SparseArray<PackageVerificationState> mPendingVerification
             = new SparseArray<PackageVerificationState>();
 
-    final ArrayList<PackageParser.Package> mDeferredDexOpt =
-            new ArrayList<PackageParser.Package>();
+    HashSet<PackageParser.Package> mDeferredDexOpt = null;
 
     /** Token for keys in mPendingVerification. */
     private int mPendingVerificationToken = 0;
@@ -514,10 +524,9 @@
         void doHandleMessage(Message msg) {
             switch (msg.what) {
                 case INIT_COPY: {
-                    if (DEBUG_INSTALL) Slog.i(TAG, "init_copy");
                     HandlerParams params = (HandlerParams) msg.obj;
                     int idx = mPendingInstalls.size();
-                    if (DEBUG_INSTALL) Slog.i(TAG, "idx=" + idx);
+                    if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params);
                     // If a bind was already initiated we dont really
                     // need to do anything. The pending install
                     // will be processed later on.
@@ -1071,9 +1080,12 @@
              * Also ensure all external libraries have had dexopt run on them.
              */
             if (mSharedLibraries.size() > 0) {
-                Iterator<String> libs = mSharedLibraries.values().iterator();
+                Iterator<SharedLibraryEntry> libs = mSharedLibraries.values().iterator();
                 while (libs.hasNext()) {
-                    String lib = libs.next();
+                    String lib = libs.next().path;
+                    if (lib == null) {
+                        continue;
+                    }
                     try {
                         if (dalvik.system.DexFile.isDexOptNeeded(lib)) {
                             libFiles.add(lib);
@@ -1277,6 +1289,10 @@
                 mDrmAppInstallObserver = null;
             }
 
+            // Now that we know all of the shared libraries, update all clients to have
+            // the correct library paths.
+            updateAllSharedLibrariesLPw();
+
             EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
                     SystemClock.uptimeMillis());
             Slog.i(TAG, "Time to scan packages: "
@@ -1517,7 +1533,7 @@
                                 + parser.getPositionDescription());
                     } else {
                         //Log.i(TAG, "Got library " + lname + " in " + lfile);
-                        mSharedLibraries.put(lname, lfile);
+                        mSharedLibraries.put(lname, new SharedLibraryEntry(lfile, null));
                     }
                     XmlUtils.skipCurrentTag(parser);
                     continue;
@@ -3249,6 +3265,7 @@
             int parseFlags, int scanMode, long currentTime, UserHandle user) {
         mLastScanError = PackageManager.INSTALL_SUCCEEDED;
         String scanPath = scanFile.getPath();
+        if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanPath);
         parseFlags |= mDefParseFlags;
         PackageParser pp = new PackageParser(scanPath);
         pp.setSeparateProcesses(mSeparateProcesses);
@@ -3278,6 +3295,7 @@
             // package.  Must look for it either under the original or real
             // package name depending on our state.
             updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
+            if (DEBUG_INSTALL && updatedPkg != null) Slog.d(TAG, "updatedPkg = " + updatedPkg);
         }
         // First check if this is a system package that may involve an update
         if (updatedPkg != null && (parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
@@ -3285,6 +3303,7 @@
                 // The path has changed from what was last scanned...  check the
                 // version of the new path against what we have stored to determine
                 // what to do.
+                if (DEBUG_INSTALL) Slog.d(TAG, "Path changing from " + ps.codePath);
                 if (pkg.mVersionCode < ps.versionCode) {
                     // The system package has been updated and the code path does not match
                     // Ignore entry. Skip it.
@@ -3298,6 +3317,7 @@
                         updatedPkg.codePath = scanFile;
                         updatedPkg.codePathString = scanFile.toString();                        
                     }
+                    updatedPkg.pkg = pkg;
                     mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
                     return null;
                 } else {
@@ -3353,6 +3373,7 @@
              */
             if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures)
                     != PackageManager.SIGNATURE_MATCH) {
+                if (DEBUG_INSTALL) Slog.d(TAG, "Signature mismatch!");
                 deletePackageLI(pkg.packageName, null, true, 0, null, false);
                 ps = null;
             } else {
@@ -3484,28 +3505,28 @@
     }
 
     public void performBootDexOpt() {
-        ArrayList<PackageParser.Package> pkgs = null;
+        HashSet<PackageParser.Package> pkgs = null;
         synchronized (mPackages) {
-            if (mDeferredDexOpt.size() > 0) {
-                pkgs = new ArrayList<PackageParser.Package>(mDeferredDexOpt);
-                mDeferredDexOpt.clear();
-            }
+            pkgs = mDeferredDexOpt;
+            mDeferredDexOpt = null;
         }
         if (pkgs != null) {
-            for (int i=0; i<pkgs.size(); i++) {
+            int i = 0;
+            for (PackageParser.Package pkg : pkgs) {
                 if (!isFirstBoot()) {
+                    i++;
                     try {
                         ActivityManagerNative.getDefault().showBootMessage(
                                 mContext.getResources().getString(
                                         com.android.internal.R.string.android_upgrading_apk,
-                                        i+1, pkgs.size()), true);
+                                        i, pkgs.size()), true);
                     } catch (RemoteException e) {
                     }
                 }
-                PackageParser.Package p = pkgs.get(i);
+                PackageParser.Package p = pkg;
                 synchronized (mInstallLock) {
                     if (!p.mDidDexOpt) {
-                        performDexOptLI(p, false, false);
+                        performDexOptLI(p, false, false, true);
                     }
                 }
             }
@@ -3527,7 +3548,27 @@
             }
         }
         synchronized (mInstallLock) {
-            return performDexOptLI(p, false, false) == DEX_OPT_PERFORMED;
+            return performDexOptLI(p, false, false, true) == DEX_OPT_PERFORMED;
+        }
+    }
+
+    private void performDexOptLibsLI(ArrayList<String> libs, boolean forceDex, boolean defer,
+            HashSet<String> done) {
+        for (int i=0; i<libs.size(); i++) {
+            PackageParser.Package libPkg;
+            String libName;
+            synchronized (mPackages) {
+                libName = libs.get(i);
+                SharedLibraryEntry lib = mSharedLibraries.get(libName);
+                if (lib != null && lib.apk != null) {
+                    libPkg = mPackages.get(lib.apk);
+                } else {
+                    libPkg = null;
+                }
+            }
+            if (libPkg != null && !done.contains(libName)) {
+                performDexOptLI(libPkg, forceDex, defer, done);
+            }
         }
     }
 
@@ -3536,14 +3577,27 @@
     static final int DEX_OPT_DEFERRED = 2;
     static final int DEX_OPT_FAILED = -1;
 
-    private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer) {
+    private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer,
+            HashSet<String> done) {
         boolean performed = false;
+        if (done != null) {
+            done.add(pkg.packageName);
+            if (pkg.usesLibraries != null) {
+                performDexOptLibsLI(pkg.usesLibraries, forceDex, defer, done);
+            }
+            if (pkg.usesOptionalLibraries != null) {
+                performDexOptLibsLI(pkg.usesOptionalLibraries, forceDex, defer, done);
+            }
+        }
         if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) {
             String path = pkg.mScanPath;
             int ret = 0;
             try {
                 if (forceDex || dalvik.system.DexFile.isDexOptNeeded(path)) {
                     if (!forceDex && defer) {
+                        if (mDeferredDexOpt == null) {
+                            mDeferredDexOpt = new HashSet<PackageParser.Package>();
+                        }
                         mDeferredDexOpt.add(pkg);
                         return DEX_OPT_DEFERRED;
                     } else {
@@ -3576,6 +3630,19 @@
         return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
     }
 
+    private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer,
+            boolean inclDependencies) {
+        HashSet<String> done;
+        boolean performed = false;
+        if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
+            done = new HashSet<String>();
+            done.add(pkg.packageName);
+        } else {
+            done = null;
+        }
+        return performDexOptLI(pkg, forceDex, defer, done);
+    }
+
     private boolean verifyPackageUpdateLPr(PackageSetting oldPkg, PackageParser.Package newPkg) {
         if ((oldPkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) {
             Slog.w(TAG, "Unable to update from " + oldPkg.name
@@ -3646,6 +3713,113 @@
         return res;
     }
 
+    private int addSharedLibraryLPw(final SharedLibraryEntry file, int num,
+            PackageParser.Package changingLib) {
+        if (file.path != null) {
+            mTmpSharedLibraries[num] = file.path;
+            return num+1;
+        }
+        PackageParser.Package p = mPackages.get(file.apk);
+        if (changingLib != null && changingLib.packageName.equals(file.apk)) {
+            // If we are doing this while in the middle of updating a library apk,
+            // then we need to make sure to use that new apk for determining the
+            // dependencies here.  (We haven't yet finished committing the new apk
+            // to the package manager state.)
+            if (p == null || p.packageName.equals(changingLib.packageName)) {
+                p = changingLib;
+            }
+        }
+        if (p != null) {
+            String path = p.mPath;
+            for (int i=0; i<num; i++) {
+                if (mTmpSharedLibraries[i].equals(path)) {
+                    return num;
+                }
+            }
+            mTmpSharedLibraries[num] = p.mPath;
+            return num+1;
+        }
+        return num;
+    }
+
+    private boolean updateSharedLibrariesLPw(PackageParser.Package pkg,
+            PackageParser.Package changingLib) {
+        if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) {
+            if (mTmpSharedLibraries == null ||
+                    mTmpSharedLibraries.length < mSharedLibraries.size()) {
+                mTmpSharedLibraries = new String[mSharedLibraries.size()];
+            }
+            int num = 0;
+            int N = pkg.usesLibraries != null ? pkg.usesLibraries.size() : 0;
+            for (int i=0; i<N; i++) {
+                final SharedLibraryEntry file = mSharedLibraries.get(pkg.usesLibraries.get(i));
+                if (file == null) {
+                    Slog.e(TAG, "Package " + pkg.packageName
+                            + " requires unavailable shared library "
+                            + pkg.usesLibraries.get(i) + "; failing!");
+                    mLastScanError = PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
+                    return false;
+                }
+                num = addSharedLibraryLPw(file, num, changingLib);
+            }
+            N = pkg.usesOptionalLibraries != null ? pkg.usesOptionalLibraries.size() : 0;
+            for (int i=0; i<N; i++) {
+                final SharedLibraryEntry file = mSharedLibraries.get(pkg.usesOptionalLibraries.get(i));
+                if (file == null) {
+                    Slog.w(TAG, "Package " + pkg.packageName
+                            + " desires unavailable shared library "
+                            + pkg.usesOptionalLibraries.get(i) + "; ignoring!");
+                } else {
+                    num = addSharedLibraryLPw(file, num, changingLib);
+                }
+            }
+            if (num > 0) {
+                pkg.usesLibraryFiles = new String[num];
+                System.arraycopy(mTmpSharedLibraries, 0,
+                        pkg.usesLibraryFiles, 0, num);
+            } else {
+                pkg.usesLibraryFiles = null;
+            }
+        }
+        return true;
+    }
+
+    private static boolean hasString(List<String> list, List<String> which) {
+        if (list == null) {
+            return false;
+        }
+        for (int i=list.size()-1; i>=0; i--) {
+            for (int j=which.size()-1; j>=0; j--) {
+                if (which.get(j).equals(list.get(i))) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private void updateAllSharedLibrariesLPw() {
+        for (PackageParser.Package pkg : mPackages.values()) {
+            updateSharedLibrariesLPw(pkg, null);
+        }
+    }
+
+    private ArrayList<PackageParser.Package> updateAllSharedLibrariesLPw(
+            PackageParser.Package changingPkg) {
+        ArrayList<PackageParser.Package> res = null;
+        for (PackageParser.Package pkg : mPackages.values()) {
+            if (hasString(pkg.usesLibraries, changingPkg.libraryNames)
+                    || hasString(pkg.usesOptionalLibraries, changingPkg.libraryNames)) {
+                if (res == null) {
+                    res = new ArrayList<PackageParser.Package>();
+                }
+                res.add(pkg);
+                updateSharedLibrariesLPw(pkg, changingPkg);
+            }
+        }
+        return res;
+    }
+
     private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
             int parseFlags, int scanMode, long currentTime, UserHandle user) {
         File scanFile = new File(pkg.mScanPath);
@@ -3725,42 +3899,14 @@
 
         // writer
         synchronized (mPackages) {
-            // Check all shared libraries and map to their actual file path.
-            if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) {
-                if (mTmpSharedLibraries == null ||
-                        mTmpSharedLibraries.length < mSharedLibraries.size()) {
-                    mTmpSharedLibraries = new String[mSharedLibraries.size()];
-                }
-                int num = 0;
-                int N = pkg.usesLibraries != null ? pkg.usesLibraries.size() : 0;
-                for (int i=0; i<N; i++) {
-                    final String file = mSharedLibraries.get(pkg.usesLibraries.get(i));
-                    if (file == null) {
-                        Slog.e(TAG, "Package " + pkg.packageName
-                                + " requires unavailable shared library "
-                                + pkg.usesLibraries.get(i) + "; failing!");
-                        mLastScanError = PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
-                        return null;
-                    }
-                    mTmpSharedLibraries[num] = file;
-                    num++;
-                }
-                N = pkg.usesOptionalLibraries != null ? pkg.usesOptionalLibraries.size() : 0;
-                for (int i=0; i<N; i++) {
-                    final String file = mSharedLibraries.get(pkg.usesOptionalLibraries.get(i));
-                    if (file == null) {
-                        Slog.w(TAG, "Package " + pkg.packageName
-                                + " desires unavailable shared library "
-                                + pkg.usesOptionalLibraries.get(i) + "; ignoring!");
-                    } else {
-                        mTmpSharedLibraries[num] = file;
-                        num++;
-                    }
-                }
-                if (num > 0) {
-                    pkg.usesLibraryFiles = new String[num];
-                    System.arraycopy(mTmpSharedLibraries, 0,
-                            pkg.usesLibraryFiles, 0, num);
+            if ((parseFlags&PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+                // Check all shared libraries and map to their actual file path.
+                // We only do this here for apps not on a system dir, because those
+                // are the only ones that can fail an install due to this.  We
+                // will take care of the system apps by updating all of their
+                // library paths after the scan is done.
+                if (!updateSharedLibrariesLPw(pkg, null)) {
+                    return null;
                 }
             }
 
@@ -4166,7 +4312,7 @@
         pkg.mScanPath = path;
 
         if ((scanMode&SCAN_NO_DEX) == 0) {
-            if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0)
+            if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
                     == DEX_OPT_FAILED) {
                 mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
                 return null;
@@ -4178,6 +4324,80 @@
             pkg.applicationInfo.flags |= ApplicationInfo.FLAG_FACTORY_TEST;
         }
 
+        ArrayList<PackageParser.Package> clientLibPkgs = null;
+
+        // writer
+        synchronized (mPackages) {
+            if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
+                // Only system apps can add new shared libraries.
+                if (pkg.libraryNames != null) {
+                    for (int i=0; i<pkg.libraryNames.size(); i++) {
+                        String name = pkg.libraryNames.get(i);
+                        boolean allowed = false;
+                        if (isUpdatedSystemApp(pkg)) {
+                            // New library entries can only be added through the
+                            // system image.  This is important to get rid of a lot
+                            // of nasty edge cases: for example if we allowed a non-
+                            // system update of the app to add a library, then uninstalling
+                            // the update would make the library go away, and assumptions
+                            // we made such as through app install filtering would now
+                            // have allowed apps on the device which aren't compatible
+                            // with it.  Better to just have the restriction here, be
+                            // conservative, and create many fewer cases that can negatively
+                            // impact the user experience.
+                            final PackageSetting sysPs = mSettings
+                                    .getDisabledSystemPkgLPr(pkg.packageName);
+                            if (sysPs.pkg != null && sysPs.pkg.libraryNames != null) {
+                                for (int j=0; j<sysPs.pkg.libraryNames.size(); j++) {
+                                    if (name.equals(sysPs.pkg.libraryNames.get(j))) {
+                                        allowed = true;
+                                        allowed = true;
+                                        break;
+                                    }
+                                }
+                            }
+                        } else {
+                            allowed = true;
+                        }
+                        if (allowed) {
+                            if (!mSharedLibraries.containsKey(name)) {
+                                mSharedLibraries.put(name, new SharedLibraryEntry(null,
+                                        pkg.packageName));
+                            } else if (!name.equals(pkg.packageName)) {
+                                Slog.w(TAG, "Package " + pkg.packageName + " library "
+                                        + name + " already exists; skipping");
+                            }
+                        } else {
+                            Slog.w(TAG, "Package " + pkg.packageName + " declares lib "
+                                    + name + " that is not declared on system image; skipping");
+                        }
+                    }
+                    if ((scanMode&SCAN_BOOTING) == 0) {
+                        // If we are not booting, we need to update any applications
+                        // that are clients of our shared library.  If we are booting,
+                        // this will all be done once the scan is complete.
+                        clientLibPkgs = updateAllSharedLibrariesLPw(pkg);
+                    }
+                }
+            }
+        }
+
+        // We also need to dexopt any apps that are dependent on this library.  Note that
+        // if these fail, we should abort the install since installing the library will
+        // result in some apps being broken.
+        if (clientLibPkgs != null) {
+            if ((scanMode&SCAN_NO_DEX) == 0) {
+                for (int i=0; i<clientLibPkgs.size(); i++) {
+                    PackageParser.Package clientPkg = clientLibPkgs.get(i);
+                    if (performDexOptLI(clientPkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
+                            == DEX_OPT_FAILED) {
+                        mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
+                        return null;
+                    }
+                }
+            }
+        }
+
         // Request the ActivityManager to kill the process(only for existing packages)
         // so that we do not end up in a confused state while the user is still using the older
         // version of the application while the new one gets installed.
@@ -4186,6 +4406,15 @@
                         pkg.applicationInfo.uid);
         }
 
+        // Also need to kill any apps that are dependent on the library.
+        if (clientLibPkgs != null) {
+            for (int i=0; i<clientLibPkgs.size(); i++) {
+                PackageParser.Package clientPkg = clientLibPkgs.get(i);
+                killApplication(clientPkg.applicationInfo.packageName,
+                        clientPkg.applicationInfo.uid);
+            }
+        }
+
         // writer
         synchronized (mPackages) {
             // We don't expect installation to fail beyond this point,
@@ -4591,7 +4820,7 @@
                     }
                 }
             }
-            if (chatty) {
+            if (DEBUG_REMOVE && chatty) {
                 if (r == null) {
                     r = new StringBuilder(256);
                 } else {
@@ -4627,7 +4856,7 @@
         for (i=0; i<N; i++) {
             PackageParser.Activity a = pkg.receivers.get(i);
             mReceivers.removeActivity(a, "receiver");
-            if (chatty) {
+            if (DEBUG_REMOVE && chatty) {
                 if (r == null) {
                     r = new StringBuilder(256);
                 } else {
@@ -4645,7 +4874,7 @@
         for (i=0; i<N; i++) {
             PackageParser.Activity a = pkg.activities.get(i);
             mActivities.removeActivity(a, "activity");
-            if (chatty) {
+            if (DEBUG_REMOVE && chatty) {
                 if (r == null) {
                     r = new StringBuilder(256);
                 } else {
@@ -4668,7 +4897,7 @@
             }
             if (bp != null && bp.perm == p) {
                 bp.perm = null;
-                if (chatty) {
+                if (DEBUG_REMOVE && chatty) {
                     if (r == null) {
                         r = new StringBuilder(256);
                     } else {
@@ -4687,7 +4916,7 @@
         for (i=0; i<N; i++) {
             PackageParser.Instrumentation a = pkg.instrumentation.get(i);
             mInstrumentation.remove(a.getComponentName());
-            if (chatty) {
+            if (DEBUG_REMOVE && chatty) {
                 if (r == null) {
                     r = new StringBuilder(256);
                 } else {
@@ -4699,6 +4928,31 @@
         if (r != null) {
             if (DEBUG_REMOVE) Log.d(TAG, "  Instrumentation: " + r);
         }
+
+        r = null;
+        if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
+            // Only system apps can hold shared libraries.
+            if (pkg.libraryNames != null) {
+                for (i=0; i<pkg.libraryNames.size(); i++) {
+                    String name = pkg.libraryNames.get(i);
+                    SharedLibraryEntry cur = mSharedLibraries.get(name);
+                    if (cur != null && cur.apk != null && cur.apk.equals(pkg.packageName)) {
+                        mSharedLibraries.remove(name);
+                        if (DEBUG_REMOVE && chatty) {
+                            if (r == null) {
+                                r = new StringBuilder(256);
+                            } else {
+                                r.append(' ');
+                            }
+                            r.append(name);
+                        }
+                    }
+                }
+            }
+        }
+        if (r != null) {
+            if (DEBUG_REMOVE) Log.d(TAG, "  Libraries: " + r);
+        }
     }
 
     private static final boolean isPackageFilename(String name) {
@@ -4860,7 +5114,23 @@
                                 if (origGp.grantedPermissions.contains(perm)) {
                                     allowed = true;
                                 } else {
+                                    // The system apk may have been updated with an older
+                                    // version of the one on the data partition, but which
+                                    // granted a new system permission that it didn't have
+                                    // before.  In this case we do want to allow the app to
+                                    // now get the new permission, because it is allowed by
+                                    // the system image.
                                     allowed = false;
+                                    if (sysPs.pkg != null) {
+                                        for (int j=0;
+                                                j<sysPs.pkg.requestedPermissions.size(); j++) {
+                                            if (perm.equals(
+                                                    sysPs.pkg.requestedPermissions.get(j))) {
+                                                allowed = true;
+                                                break;
+                                            }
+                                        }
+                                    }
                                 }
                             } else {
                                 allowed = true;
@@ -5557,6 +5827,7 @@
                 }
                 if ((event&REMOVE_EVENTS) != 0) {
                     if (ps != null) {
+                        if (DEBUG_REMOVE) Slog.d(TAG, "Package disappeared: " + ps);
                         removePackageLI(ps, true);
                         removedPackage = ps.name;
                         removedAppId = ps.appId;
@@ -5565,6 +5836,7 @@
 
                 if ((event&ADD_EVENTS) != 0) {
                     if (p == null) {
+                        if (DEBUG_INSTALL) Slog.d(TAG, "New file appeared: " + fullPath);
                         p = scanPackageLI(fullPath,
                                 (mIsRom ? PackageParser.PARSE_IS_SYSTEM
                                         | PackageParser.PARSE_IS_SYSTEM_DIR: 0) |
@@ -6126,7 +6398,7 @@
         final boolean startCopy() {
             boolean res;
             try {
-                if (DEBUG_INSTALL) Slog.i(TAG, "startCopy");
+                if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
 
                 if (++mRetries > MAX_RETRIES) {
                     Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
@@ -6170,6 +6442,13 @@
         }
 
         @Override
+        public String toString() {
+            return "MeasureParams{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " " + mStats.packageName + "}";
+        }
+
+        @Override
         void handleStartCopy() throws RemoteException {
             synchronized (mInstallLock) {
                 mSuccess = getPackageSizeInfoLI(mStats.packageName, mStats.userHandle, mStats);
@@ -6258,6 +6537,13 @@
             this.encryptionParams = encryptionParams;
         }
 
+        @Override
+        public String toString() {
+            return "InstallParams{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " " + mPackageURI + "}";
+        }
+
         public ManifestDigest getManifestDigest() {
             if (verificationParams == null) {
                 return null;
@@ -6671,6 +6957,13 @@
             }
         }
 
+        @Override
+        public String toString() {
+            return "MoveParams{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " " + packageName + "}";
+        }
+
         public void handleStartCopy() throws RemoteException {
             mRet = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
             // Check for storage space on target medium
@@ -7582,6 +7875,7 @@
         // Remember this for later, in case we need to rollback this install
         String pkgName = pkg.packageName;
 
+        if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + pkg);
         boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists();
         synchronized(mPackages) {
             if (mSettings.mRenamedPackages.containsKey(pkgName)) {
@@ -7638,6 +7932,7 @@
         // First find the old package info and check signatures
         synchronized(mPackages) {
             oldPackage = mPackages.get(pkgName);
+            if (DEBUG_INSTALL) Slog.d(TAG, "replacePackageLI: new=" + pkg + ", old=" + oldPackage);
             if (compareSignatures(oldPackage.mSignatures, pkg.mSignatures)
                     != PackageManager.SIGNATURE_MATCH) {
                 Slog.w(TAG, "New package has a different signature: " + pkgName);
@@ -7663,6 +7958,8 @@
         boolean deletedPkg = true;
         boolean updatedSettings = false;
 
+        if (DEBUG_INSTALL) Slog.d(TAG, "replaceNonSystemPackageLI: new=" + pkg + ", old="
+                + deletedPackage);
         long origUpdateTime;
         if (pkg.mExtras != null) {
             origUpdateTime = ((PackageSetting)pkg.mExtras).lastUpdateTime;
@@ -7700,6 +7997,7 @@
             // scanPackageLocked, unless those directories existed before we even tried to
             // install.
             if(updatedSettings) {
+                if (DEBUG_INSTALL) Slog.d(TAG, "Install failed, rolling pack: " + pkgName);
                 deletePackageLI(
                         pkgName, null, true,
                         PackageManager.DELETE_KEEP_DATA,
@@ -7708,6 +8006,7 @@
             // Since we failed to install the new package we need to restore the old
             // package that we deleted.
             if(deletedPkg) {
+                if (DEBUG_INSTALL) Slog.d(TAG, "Install failed, reinstalling: " + deletedPackage);
                 File restoreFile = new File(deletedPackage.mPath);
                 // Parse old package
                 boolean oldOnSd = isExternal(deletedPackage);
@@ -7737,6 +8036,8 @@
     private void replaceSystemPackageLI(PackageParser.Package deletedPackage,
             PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user,
             String installerPackageName, PackageInstalledInfo res) {
+        if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg
+                + ", old=" + deletedPackage);
         PackageParser.Package newPackage = null;
         boolean updatedSettings = false;
         parseFlags |= PackageManager.INSTALL_REPLACE_EXISTING |
@@ -7859,7 +8160,7 @@
             return;
         }
 
-        Log.d(TAG, "New package installed in " + newPackage.mPath);
+        if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + newPackage.mPath);
 
         synchronized (mPackages) {
             updatePermissionsLPw(newPackage.packageName, newPackage,
@@ -7889,6 +8190,7 @@
         // Result object to be returned
         res.returnCode = PackageManager.INSTALL_SUCCEEDED;
 
+        if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile);
         // Retrieve PackageSettings and parse package
         int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
                 | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
@@ -7950,14 +8252,18 @@
                     pkg.setPackageName(oldName);
                     pkgName = pkg.packageName;
                     replace = true;
+                    if (DEBUG_INSTALL) Slog.d(TAG, "Replacing existing renamed package: oldName="
+                            + oldName + " pkgName=" + pkgName);
                 } else if (mPackages.containsKey(pkgName)) {
                     // This package, under its official name, already exists
                     // on the device; we should replace it.
                     replace = true;
+                    if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing pacakge: " + pkgName);
                 }
             }
             PackageSetting ps = mSettings.mPackages.get(pkgName);
             if (ps != null) {
+                if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps);
                 oldCodePath = mSettings.mPackages.get(pkgName).codePathString;
                 if (ps.pkg != null && ps.pkg.applicationInfo != null) {
                     systemApp = (ps.pkg.applicationInfo.flags &
@@ -8104,6 +8410,7 @@
             return;
         }
 
+        if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageAsUser: pkg=" + packageName + " user=" + userId);
         // Queue up an async operation since the package deletion may take a little while.
         mHandler.post(new Runnable() {
             public void run() {
@@ -8151,6 +8458,7 @@
         boolean removedForAllUsers = false;
         boolean systemUpdate = false;
         synchronized (mInstallLock) {
+            if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageX: pkg=" + packageName + " user=" + userId);
             res = deletePackageLI(packageName,
                     (flags & PackageManager.DELETE_ALL_USERS) != 0
                             ? UserHandle.ALL : new UserHandle(userId),
@@ -8159,6 +8467,8 @@
             if (res && !systemUpdate && mPackages.get(packageName) == null) {
                 removedForAllUsers = true;
             }
+            if (DEBUG_REMOVE) Slog.d(TAG, "delete res: systemUpdate=" + systemUpdate
+                    + " removedForAllUsers=" + removedForAllUsers);
         }
 
         if (res) {
@@ -8234,6 +8544,7 @@
     private void removePackageDataLI(PackageSetting ps, PackageRemovedInfo outInfo,
             int flags, boolean writeSettings) {
         String packageName = ps.name;
+        if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + ps);
         removePackageLI(ps, (flags&REMOVE_CHATTY) != 0);
         // Retrieve object to delete permissions for shared user later on
         final PackageSetting deletedPs;
@@ -8289,11 +8600,13 @@
         synchronized (mPackages) {
             disabledPs = mSettings.getDisabledSystemPkgLPr(newPs.name);
         }
+        if (DEBUG_REMOVE) Slog.d(TAG, "deleteSystemPackageLI: newPs=" + newPs
+                + " disabledPs=" + disabledPs);
         if (disabledPs == null) {
             Slog.w(TAG, "Attempt to delete unknown system package "+ newPs.name);
             return false;
-        } else {
-            Log.i(TAG, "Deleting system pkg from data partition");
+        } else if (DEBUG_REMOVE) {
+            Slog.d(TAG, "Deleting system pkg from data partition");
         }
         // Delete the updated package
         outInfo.isRemovedPackageSystemUpdate = true;
@@ -8317,6 +8630,7 @@
             NativeLibraryHelper.removeNativeBinariesLI(newPs.nativeLibraryPathString);
         }
         // Install the system package
+        if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
         PackageParser.Package newPkg = scanPackageLI(disabledPs.codePath,
                 PackageParser.PARSE_MUST_BE_APK | PackageParser.PARSE_IS_SYSTEM,
                 SCAN_MONITOR | SCAN_NO_PATHS, 0, null);
@@ -8366,6 +8680,7 @@
             Slog.w(TAG, "Attempt to delete null packageName.");
             return false;
         }
+        if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user);
         PackageSetting ps;
         boolean dataOnly = false;
         int removeUser = -1;
@@ -8376,11 +8691,14 @@
                 Slog.w(TAG, "Package named '" + packageName + "' doesn't exist.");
                 return false;
             }
-            if (user != null
+            if ((!isSystemApp(ps) || (flags&PackageManager.DELETE_SYSTEM_APP) != 0) && user != null
                     && user.getIdentifier() != UserHandle.USER_ALL) {
                 // The caller is asking that the package only be deleted for a single
                 // user.  To do this, we just mark its uninstalled state and delete
-                // its data.
+                // its data.  If this is a system app, we only allow this to happen if
+                // they have set the special DELETE_SYSTEM_APP which requests different
+                // semantics than normal for uninstalling system apps.
+                if (DEBUG_REMOVE) Slog.d(TAG, "Only deleting for single user");
                 ps.setUserState(user.getIdentifier(),
                         COMPONENT_ENABLED_STATE_DEFAULT,
                         false, //installed
@@ -8392,12 +8710,14 @@
                         // Other user still have this package installed, so all
                         // we need to do is clear this user's data and save that
                         // it is uninstalled.
+                        if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users");
                         removeUser = user.getIdentifier();
                         appId = ps.appId;
                         mSettings.writePackageRestrictionsLPr(removeUser);
                     } else {
                         // We need to set it back to 'installed' so the uninstall
                         // broadcasts will be sent correctly.
+                        if (DEBUG_REMOVE) Slog.d(TAG, "Not installed by other users, full delete");
                         ps.setInstalled(true, user.getIdentifier());
                     }
                 } else {
@@ -8405,6 +8725,7 @@
                     // other users still have this package installed, so all
                     // we need to do is clear this user's data and save that
                     // it is uninstalled.
+                    if (DEBUG_REMOVE) Slog.d(TAG, "Deleting system app");
                     removeUser = user.getIdentifier();
                     appId = ps.appId;
                     mSettings.writePackageRestrictionsLPr(removeUser);
@@ -8415,6 +8736,7 @@
         if (removeUser >= 0) {
             // From above, we determined that we are deleting this only
             // for a single user.  Continue the work here.
+            if (DEBUG_REMOVE) Slog.d(TAG, "Updating install state for user: " + removeUser);
             if (outInfo != null) {
                 outInfo.removedPackage = packageName;
                 outInfo.removedAppId = appId;
@@ -8427,17 +8749,18 @@
 
         if (dataOnly) {
             // Delete application data first
+            if (DEBUG_REMOVE) Slog.d(TAG, "Removing package data only");
             removePackageDataLI(ps, outInfo, flags, writeSettings);
             return true;
         }
         boolean ret = false;
         if (isSystemApp(ps)) {
-            Log.i(TAG, "Removing system package:" + ps.name);
+            if (DEBUG_REMOVE) Slog.d(TAG, "Removing system package:" + ps.name);
             // When an updated system application is deleted we delete the existing resources as well and
             // fall back to existing code in system partition
             ret = deleteSystemPackageLI(ps, flags, outInfo, writeSettings);
         } else {
-            Log.i(TAG, "Removing non-system package:" + ps.name);
+            if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package:" + ps.name);
             // Kill application pre-emptively especially for apps on sd.
             killApplication(packageName, ps.appId);
             ret = deleteInstalledPackageLI(ps, deleteCodeAndResources, flags, outInfo,
@@ -9408,7 +9731,15 @@
                     pw.print("  ");
                     pw.print(name);
                     pw.print(" -> ");
-                    pw.println(mSharedLibraries.get(name));
+                    SharedLibraryEntry ent = mSharedLibraries.get(name);
+                    if (ent.path != null) {
+                        pw.print("(jar) ");
+                        pw.print(ent.path);
+                    } else {
+                        pw.print("(apk) ");
+                        pw.print(ent.apk);
+                    }
+                    pw.println();
                 }
             }
 
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index 13f514b..e645078 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -32,7 +32,6 @@
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.XmlUtils;
-import com.android.server.IntentResolver;
 import com.android.server.pm.PackageManagerService.DumpState;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -2657,6 +2656,162 @@
         ApplicationInfo.FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE",
     };
 
+    void dumpPackageLPr(PrintWriter pw, String prefix, PackageSetting ps, SimpleDateFormat sdf,
+            Date date, List<UserInfo> users) {
+        pw.print(prefix); pw.print("Package [");
+            pw.print(ps.realName != null ? ps.realName : ps.name);
+            pw.print("] (");
+            pw.print(Integer.toHexString(System.identityHashCode(ps)));
+            pw.println("):");
+
+        if (ps.realName != null) {
+            pw.print(prefix); pw.print("  compat name=");
+            pw.println(ps.name);
+        }
+
+        pw.print(prefix); pw.print("  userId="); pw.print(ps.appId);
+                pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids));
+        if (ps.sharedUser != null) {
+            pw.print(prefix); pw.print("  sharedUser="); pw.println(ps.sharedUser);
+        }
+        pw.print(prefix); pw.print("  pkg="); pw.println(ps.pkg);
+        pw.print(prefix); pw.print("  codePath="); pw.println(ps.codePathString);
+        pw.print(prefix); pw.print("  resourcePath="); pw.println(ps.resourcePathString);
+        pw.print(prefix); pw.print("  nativeLibraryPath="); pw.println(ps.nativeLibraryPathString);
+        pw.print(prefix); pw.print("  versionCode="); pw.print(ps.versionCode);
+        if (ps.pkg != null) {
+            pw.print(" targetSdk="); pw.print(ps.pkg.applicationInfo.targetSdkVersion);
+        }
+        pw.println();
+        if (ps.pkg != null) {
+            pw.print(prefix); pw.print("  versionName="); pw.println(ps.pkg.mVersionName);
+            pw.print(prefix); pw.print("  applicationInfo=");
+                pw.println(ps.pkg.applicationInfo.toString());
+            pw.print(prefix); pw.print("  flags="); printFlags(pw, ps.pkg.applicationInfo.flags,
+                    FLAG_DUMP_SPEC); pw.println();
+            pw.print(prefix); pw.print("  dataDir="); pw.println(ps.pkg.applicationInfo.dataDir);
+            if (ps.pkg.mOperationPending) {
+                pw.print(prefix); pw.println("  mOperationPending=true");
+            }
+            pw.print(prefix); pw.print("  supportsScreens=[");
+            boolean first = true;
+            if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) {
+                if (!first)
+                    pw.print(", ");
+                first = false;
+                pw.print("small");
+            }
+            if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) {
+                if (!first)
+                    pw.print(", ");
+                first = false;
+                pw.print("medium");
+            }
+            if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
+                if (!first)
+                    pw.print(", ");
+                first = false;
+                pw.print("large");
+            }
+            if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
+                if (!first)
+                    pw.print(", ");
+                first = false;
+                pw.print("xlarge");
+            }
+            if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
+                if (!first)
+                    pw.print(", ");
+                first = false;
+                pw.print("resizeable");
+            }
+            if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
+                if (!first)
+                    pw.print(", ");
+                first = false;
+                pw.print("anyDensity");
+            }
+            pw.println("]");
+            if (ps.pkg.libraryNames != null && ps.pkg.libraryNames.size() > 0) {
+                pw.print(prefix); pw.println("  libraries:");
+                for (int i=0; i<ps.pkg.libraryNames.size(); i++) {
+                    pw.print(prefix); pw.print("    "); pw.println(ps.pkg.libraryNames.get(i));
+                }
+            }
+            if (ps.pkg.usesLibraries != null && ps.pkg.usesLibraries.size() > 0) {
+                pw.print(prefix); pw.println("  usesLibraries:");
+                for (int i=0; i<ps.pkg.usesLibraries.size(); i++) {
+                    pw.print(prefix); pw.print("    "); pw.println(ps.pkg.usesLibraries.get(i));
+                }
+            }
+            if (ps.pkg.usesOptionalLibraries != null
+                    && ps.pkg.usesOptionalLibraries.size() > 0) {
+                pw.print(prefix); pw.println("  usesOptionalLibraries:");
+                for (int i=0; i<ps.pkg.usesOptionalLibraries.size(); i++) {
+                    pw.print(prefix); pw.print("    ");
+                        pw.println(ps.pkg.usesOptionalLibraries.get(i));
+                }
+            }
+            if (ps.pkg.usesLibraryFiles != null
+                    && ps.pkg.usesLibraryFiles.length > 0) {
+                pw.print(prefix); pw.println("  usesLibraryFiles:");
+                for (int i=0; i<ps.pkg.usesLibraryFiles.length; i++) {
+                    pw.print(prefix); pw.print("    "); pw.println(ps.pkg.usesLibraryFiles[i]);
+                }
+            }
+        }
+        pw.print(prefix); pw.print("  timeStamp=");
+            date.setTime(ps.timeStamp);
+            pw.println(sdf.format(date));
+        pw.print(prefix); pw.print("  firstInstallTime=");
+            date.setTime(ps.firstInstallTime);
+            pw.println(sdf.format(date));
+        pw.print(prefix); pw.print("  lastUpdateTime=");
+            date.setTime(ps.lastUpdateTime);
+            pw.println(sdf.format(date));
+        if (ps.installerPackageName != null) {
+            pw.print(prefix); pw.print("  installerPackageName=");
+                    pw.println(ps.installerPackageName);
+        }
+        pw.print(prefix); pw.print("  signatures="); pw.println(ps.signatures);
+        pw.print(prefix); pw.print("  permissionsFixed="); pw.print(ps.permissionsFixed);
+                pw.print(" haveGids="); pw.print(ps.haveGids);
+                pw.print(" installStatus="); pw.println(ps.installStatus);
+        pw.print(prefix); pw.print("  pkgFlags="); printFlags(pw, ps.pkgFlags, FLAG_DUMP_SPEC);
+                pw.println();
+        for (UserInfo user : users) {
+            pw.print(prefix); pw.print("  User "); pw.print(user.id); pw.print(": ");
+            pw.print(" installed=");
+            pw.print(ps.getInstalled(user.id));
+            pw.print(" stopped=");
+            pw.print(ps.getStopped(user.id));
+            pw.print(" notLaunched=");
+            pw.print(ps.getNotLaunched(user.id));
+            pw.print(" enabled=");
+            pw.println(ps.getEnabled(user.id));
+            HashSet<String> cmp = ps.getDisabledComponents(user.id);
+            if (cmp != null && cmp.size() > 0) {
+                pw.print(prefix); pw.println("    disabledComponents:");
+                for (String s : cmp) {
+                    pw.print(prefix); pw.print("    "); pw.println(s);
+                }
+            }
+            cmp = ps.getEnabledComponents(user.id);
+            if (cmp != null && cmp.size() > 0) {
+                pw.print(prefix); pw.println("    enabledComponents:");
+                for (String s : cmp) {
+                    pw.print(prefix); pw.print("    "); pw.println(s);
+                }
+            }
+        }
+        if (ps.grantedPermissions.size() > 0) {
+            pw.print(prefix); pw.println("  grantedPermissions:");
+            for (String s : ps.grantedPermissions) {
+                pw.print(prefix); pw.print("    "); pw.println(s);
+            }
+        }
+    }
+
     void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState) {
         final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         final Date date = new Date();
@@ -2678,123 +2833,7 @@
                 pw.println("Packages:");
                 printedSomething = true;
             }
-            pw.print("  Package [");
-                pw.print(ps.realName != null ? ps.realName : ps.name);
-                pw.print("] (");
-                pw.print(Integer.toHexString(System.identityHashCode(ps)));
-                pw.println("):");
-
-            if (ps.realName != null) {
-                pw.print("    compat name=");
-                pw.println(ps.name);
-            }
-
-            pw.print("    userId="); pw.print(ps.appId);
-            pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids));
-            pw.print("    sharedUser="); pw.println(ps.sharedUser);
-            pw.print("    pkg="); pw.println(ps.pkg);
-            pw.print("    codePath="); pw.println(ps.codePathString);
-            pw.print("    resourcePath="); pw.println(ps.resourcePathString);
-            pw.print("    nativeLibraryPath="); pw.println(ps.nativeLibraryPathString);
-            pw.print("    versionCode="); pw.println(ps.versionCode);
-            if (ps.pkg != null) {
-                pw.print("    applicationInfo="); pw.println(ps.pkg.applicationInfo.toString());
-                pw.print("    flags="); printFlags(pw, ps.pkg.applicationInfo.flags, FLAG_DUMP_SPEC); pw.println();
-                pw.print("    versionName="); pw.println(ps.pkg.mVersionName);
-                pw.print("    dataDir="); pw.println(ps.pkg.applicationInfo.dataDir);
-                pw.print("    targetSdk="); pw.println(ps.pkg.applicationInfo.targetSdkVersion);
-                if (ps.pkg.mOperationPending) {
-                    pw.println("    mOperationPending=true");
-                }
-                pw.print("    supportsScreens=[");
-                boolean first = true;
-                if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) {
-                    if (!first)
-                        pw.print(", ");
-                    first = false;
-                    pw.print("small");
-                }
-                if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) {
-                    if (!first)
-                        pw.print(", ");
-                    first = false;
-                    pw.print("medium");
-                }
-                if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
-                    if (!first)
-                        pw.print(", ");
-                    first = false;
-                    pw.print("large");
-                }
-                if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
-                    if (!first)
-                        pw.print(", ");
-                    first = false;
-                    pw.print("xlarge");
-                }
-                if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
-                    if (!first)
-                        pw.print(", ");
-                    first = false;
-                    pw.print("resizeable");
-                }
-                if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
-                    if (!first)
-                        pw.print(", ");
-                    first = false;
-                    pw.print("anyDensity");
-                }
-                pw.println("]");
-            }
-            pw.print("    timeStamp=");
-                date.setTime(ps.timeStamp);
-                pw.println(sdf.format(date));
-            pw.print("    firstInstallTime=");
-                date.setTime(ps.firstInstallTime);
-                pw.println(sdf.format(date));
-            pw.print("    lastUpdateTime=");
-                date.setTime(ps.lastUpdateTime);
-                pw.println(sdf.format(date));
-            if (ps.installerPackageName != null) {
-                pw.print("    installerPackageName="); pw.println(ps.installerPackageName);
-            }
-            pw.print("    signatures="); pw.println(ps.signatures);
-            pw.print("    permissionsFixed="); pw.print(ps.permissionsFixed);
-                    pw.print(" haveGids="); pw.print(ps.haveGids);
-                    pw.print(" installStatus="); pw.println(ps.installStatus);
-            pw.print("    pkgFlags="); printFlags(pw, ps.pkgFlags, FLAG_DUMP_SPEC);
-                    pw.println();
-            for (UserInfo user : users) {
-                pw.print("    User "); pw.print(user.id); pw.print(": ");
-                pw.print(" installed=");
-                pw.print(ps.getInstalled(user.id));
-                pw.print(" stopped=");
-                pw.print(ps.getStopped(user.id));
-                pw.print(" notLaunched=");
-                pw.print(ps.getNotLaunched(user.id));
-                pw.print(" enabled=");
-                pw.println(ps.getEnabled(user.id));
-                HashSet<String> cmp = ps.getDisabledComponents(user.id);
-                if (cmp != null && cmp.size() > 0) {
-                    pw.println("      disabledComponents:");
-                    for (String s : cmp) {
-                        pw.print("      "); pw.println(s);
-                    }
-                }
-                cmp = ps.getEnabledComponents(user.id);
-                if (cmp != null && cmp.size() > 0) {
-                    pw.println("      enabledComponents:");
-                    for (String s : cmp) {
-                        pw.print("      "); pw.println(s);
-                    }
-                }
-            }
-            if (ps.grantedPermissions.size() > 0) {
-                pw.println("    grantedPermissions:");
-                for (String s : ps.grantedPermissions) {
-                    pw.print("      "); pw.println(s);
-                }
-            }
+            dumpPackageLPr(pw, "  ", ps, sdf, date, users);
         }
 
         printedSomething = false;
@@ -2830,27 +2869,7 @@
                     pw.println("Hidden system packages:");
                     printedSomething = true;
                 }
-                pw.print("  Package [");
-                pw.print(ps.realName != null ? ps.realName : ps.name);
-                pw.print("] (");
-                pw.print(Integer.toHexString(System.identityHashCode(ps)));
-                pw.println("):");
-                if (ps.realName != null) {
-                    pw.print("    compat name=");
-                    pw.println(ps.name);
-                }
-                if (ps.pkg != null && ps.pkg.applicationInfo != null) {
-                    pw.print("    applicationInfo=");
-                    pw.println(ps.pkg.applicationInfo.toString());
-                }
-                pw.print("    userId=");
-                pw.println(ps.appId);
-                pw.print("    sharedUser=");
-                pw.println(ps.sharedUser);
-                pw.print("    codePath=");
-                pw.println(ps.codePathString);
-                pw.print("    resourcePath=");
-                pw.println(ps.resourcePathString);
+                dumpPackageLPr(pw, "  ", ps, sdf, date, users);
             }
         }
     }
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();
diff --git a/tests/SharedLibrary/client/Android.mk b/tests/SharedLibrary/client/Android.mk
new file mode 100644
index 0000000..60ef92a
--- /dev/null
+++ b/tests/SharedLibrary/client/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_APK_LIBRARIES := SharedLibrary
+
+LOCAL_PACKAGE_NAME := SharedLibraryClient
+
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/SharedLibrary/client/AndroidManifest.xml b/tests/SharedLibrary/client/AndroidManifest.xml
new file mode 100644
index 0000000..c6a43c0
--- /dev/null
+++ b/tests/SharedLibrary/client/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.android.test.lib_client">
+    <application android:label="@string/app_title">
+        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="com.google.android.test.shared_library" />
+        <activity android:name="ActivityMain">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/SharedLibrary/client/res/values/strings.xml b/tests/SharedLibrary/client/res/values/strings.xml
new file mode 100644
index 0000000..3757a25
--- /dev/null
+++ b/tests/SharedLibrary/client/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+    <string name="app_title">SharedLibrary client</string>
+</resources>
diff --git a/tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java b/tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java
new file mode 100644
index 0000000..d6121a5
--- /dev/null
+++ b/tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java
@@ -0,0 +1,35 @@
+/*
+ * 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 com.google.android.test.lib_client;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+import com.google.android.test.shared_library.SharedLibraryMain;
+
+public class ActivityMain extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        TextView content = new TextView(this);
+        content.setText("Library version: " + SharedLibraryMain.getVersion(this) + "!");
+
+        SharedLibraryMain.ensureVersion(this, SharedLibraryMain.VERSION_BASE);
+        setContentView(content);
+    }
+}
diff --git a/tests/SharedLibrary/lib/Android.mk b/tests/SharedLibrary/lib/Android.mk
new file mode 100644
index 0000000..c19e23a
--- /dev/null
+++ b/tests/SharedLibrary/lib/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := SharedLibrary
+
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/SharedLibrary/lib/AndroidManifest.xml b/tests/SharedLibrary/lib/AndroidManifest.xml
new file mode 100644
index 0000000..31fac20
--- /dev/null
+++ b/tests/SharedLibrary/lib/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.android.test.shared_library"
+    android:versionCode="2">
+    <application android:label="SharedLibrary">
+        <library android:name="com.google.android.test.shared_library" />
+        <activity android:name="ActivityMain">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/SharedLibrary/lib/res/values/strings.xml b/tests/SharedLibrary/lib/res/values/strings.xml
new file mode 100644
index 0000000..bbfb0b4
--- /dev/null
+++ b/tests/SharedLibrary/lib/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="upgrade_title">Upgrade required</string>
+    <string name="upgrade_body"><xliff:g id="app">%1$s</xliff:g> requires a newer version
+            of <xliff:g id="lib">%2$s</xliff:g> to run.</string>
+    <string name="upgrade_button">Upgrade</string>
+</resources>
diff --git a/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/ActivityMain.java b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/ActivityMain.java
new file mode 100644
index 0000000..895aced
--- /dev/null
+++ b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/ActivityMain.java
@@ -0,0 +1,32 @@
+/*
+ * 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 com.google.android.test.shared_library;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class ActivityMain extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        TextView content = new TextView(this);
+        content.setText("Dummy main entry for this apk; not really needed...");
+        setContentView(content);
+    }
+}
diff --git a/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/SharedLibraryMain.java b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/SharedLibraryMain.java
new file mode 100644
index 0000000..c1cd925
--- /dev/null
+++ b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/SharedLibraryMain.java
@@ -0,0 +1,87 @@
+/*
+ * 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 com.google.android.test.shared_library;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+
+public class SharedLibraryMain {
+    private static String LIBRARY_PACKAGE = "com.google.android.test.shared_library";
+
+    /**
+     * Base version of the library.
+     */
+    public static int VERSION_BASE = 1;
+
+    /**
+     * The second version of the library.
+     */
+    public static int VERSION_SECOND = 2;
+
+    public static int getVersion(Context context) {
+        PackageInfo pi = null;
+        try {
+            pi = context.getPackageManager().getPackageInfo(LIBRARY_PACKAGE, 0);
+            return pi.versionCode;
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new IllegalStateException("Can't find my package!", e);
+        }
+    }
+
+    public static void ensureVersion(Activity activity, int minVersion) {
+        if (getVersion(activity) >= minVersion) {
+            return;
+        }
+
+        // The current version of the library does not meet the required version.  Show
+        // a dialog to inform the user and have them update to the current version.
+        // Note that updating the library will be necessity mean killing the current
+        // application (so it can be re-started with the new version, so there is no
+        // reason to return a result here.
+        final Context context;
+        try {
+            context = activity.createPackageContext(LIBRARY_PACKAGE, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new IllegalStateException("Can't find my package!", e);
+        }
+
+        // Display the dialog.  Note that we don't need to deal with activity lifecycle
+        // stuff because if the activity gets recreated, it will first call through to
+        // ensureVersion(), causing us to either re-display the dialog if needed or let
+        // it now proceed.
+        final Resources res = context.getResources();
+        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+        builder.setTitle(res.getText(R.string.upgrade_title));
+        builder.setMessage(res.getString(R.string.upgrade_body,
+                activity.getApplicationInfo().loadLabel(activity.getPackageManager()),
+                context.getApplicationInfo().loadLabel(context.getPackageManager())));
+        builder.setPositiveButton(res.getText(R.string.upgrade_button),
+                new Dialog.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        // Launch play store.
+                    }
+                });
+        builder.show();
+    }
+}