Merge "MIDI Manager changes:"
diff --git a/api/current.txt b/api/current.txt
index b22dd1c1..612d16d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10973,6 +10973,8 @@
   public class ImageFormat {
     ctor public ImageFormat();
     method public static int getBitsPerPixel(int);
+    field public static final int DEPTH16 = 1144402265; // 0x44363159
+    field public static final int DEPTH_POINT_CLOUD = 257; // 0x101
     field public static final int JPEG = 256; // 0x100
     field public static final int NV16 = 16; // 0x10
     field public static final int NV21 = 17; // 0x11
diff --git a/api/system-current.txt b/api/system-current.txt
index 2b8ce98..ff9809f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -11248,6 +11248,8 @@
   public class ImageFormat {
     ctor public ImageFormat();
     method public static int getBitsPerPixel(int);
+    field public static final int DEPTH16 = 1144402265; // 0x44363159
+    field public static final int DEPTH_POINT_CLOUD = 257; // 0x101
     field public static final int JPEG = 256; // 0x100
     field public static final int NV16 = 16; // 0x10
     field public static final int NV21 = 17; // 0x11
@@ -15139,6 +15141,7 @@
 
   public final class AudioAttributes implements android.os.Parcelable {
     method public int describeContents();
+    method public int getAllFlags();
     method public int getCapturePreset();
     method public int getContentType();
     method public int getFlags();
@@ -15180,6 +15183,7 @@
     method public android.media.AudioAttributes.Builder setCapturePreset(int);
     method public android.media.AudioAttributes.Builder setContentType(int);
     method public android.media.AudioAttributes.Builder setFlags(int);
+    method public android.media.AudioAttributes.Builder setInternalCapturePreset(int);
     method public android.media.AudioAttributes.Builder setLegacyStreamType(int);
     method public android.media.AudioAttributes.Builder setUsage(int);
   }
@@ -15412,6 +15416,7 @@
 
   public class AudioRecord {
     ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException;
+    ctor public AudioRecord(android.media.AudioAttributes, android.media.AudioFormat, int, int) throws java.lang.IllegalArgumentException;
     method public int getAudioFormat();
     method public int getAudioSessionId();
     method public int getAudioSource();
@@ -16550,7 +16555,9 @@
   public final class MediaRecorder.AudioSource {
     field public static final int CAMCORDER = 5; // 0x5
     field public static final int DEFAULT = 0; // 0x0
+    field public static final int HOTWORD = 1999; // 0x7cf
     field public static final int MIC = 1; // 0x1
+    field public static final int RADIO_TUNER = 1998; // 0x7ce
     field public static final int REMOTE_SUBMIX = 8; // 0x8
     field public static final int VOICE_CALL = 4; // 0x4
     field public static final int VOICE_COMMUNICATION = 7; // 0x7
diff --git a/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java b/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java
index ce87329..161c339 100644
--- a/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java
+++ b/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java
@@ -26,32 +26,32 @@
  * @hide
  */
 public final class BluetoothActivityEnergyInfo implements Parcelable {
+    private final long mTimestamp;
     private final int mBluetoothStackState;
     private final int mControllerTxTimeMs;
     private final int mControllerRxTimeMs;
     private final int mControllerIdleTimeMs;
     private final int mControllerEnergyUsed;
-    private final long timestamp;
 
     public static final int BT_STACK_STATE_INVALID = 0;
     public static final int BT_STACK_STATE_STATE_ACTIVE = 1;
     public static final int BT_STACK_STATE_STATE_SCANNING = 2;
     public static final int BT_STACK_STATE_STATE_IDLE = 3;
 
-    public BluetoothActivityEnergyInfo(int stackState, int txTime, int rxTime,
-            int idleTime, int energyUsed) {
+    public BluetoothActivityEnergyInfo(long timestamp, int stackState,
+                                       int txTime, int rxTime, int idleTime, int energyUsed) {
+        mTimestamp = timestamp;
         mBluetoothStackState = stackState;
         mControllerTxTimeMs = txTime;
         mControllerRxTimeMs = rxTime;
         mControllerIdleTimeMs = idleTime;
         mControllerEnergyUsed = energyUsed;
-        timestamp = System.currentTimeMillis();
     }
 
     @Override
     public String toString() {
         return "BluetoothActivityEnergyInfo{"
-            + " timestamp=" + timestamp
+            + " mTimestamp=" + mTimestamp
             + " mBluetoothStackState=" + mBluetoothStackState
             + " mControllerTxTimeMs=" + mControllerTxTimeMs
             + " mControllerRxTimeMs=" + mControllerRxTimeMs
@@ -63,13 +63,14 @@
     public static final Parcelable.Creator<BluetoothActivityEnergyInfo> CREATOR =
             new Parcelable.Creator<BluetoothActivityEnergyInfo>() {
         public BluetoothActivityEnergyInfo createFromParcel(Parcel in) {
+            long timestamp = in.readLong();
             int stackState = in.readInt();
             int txTime = in.readInt();
             int rxTime = in.readInt();
             int idleTime = in.readInt();
             int energyUsed = in.readInt();
-            return new BluetoothActivityEnergyInfo(stackState, txTime, rxTime,
-                    idleTime, energyUsed);
+            return new BluetoothActivityEnergyInfo(timestamp, stackState,
+                    txTime, rxTime, idleTime, energyUsed);
         }
         public BluetoothActivityEnergyInfo[] newArray(int size) {
             return new BluetoothActivityEnergyInfo[size];
@@ -77,6 +78,7 @@
     };
 
     public void writeToParcel(Parcel out, int flags) {
+        out.writeLong(mTimestamp);
         out.writeInt(mBluetoothStackState);
         out.writeInt(mControllerTxTimeMs);
         out.writeInt(mControllerRxTimeMs);
@@ -123,11 +125,12 @@
     public int getControllerEnergyUsed() {
         return mControllerEnergyUsed;
     }
+
     /**
-     * @return timestamp(wall clock) of record creation
+     * @return timestamp(real time elapsed in milliseconds since boot) of record creation.
      */
     public long getTimeStamp() {
-        return timestamp;
+        return mTimestamp;
     }
 
     /**
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 410849a..1fca920 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -444,8 +444,10 @@
             }
             return defValue;
         } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+            final TypedValue value = mValue;
+            getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
             throw new UnsupportedOperationException(
-                    "Failed to resolve attribute at index " + index);
+                    "Failed to resolve attribute at index " + index + ": " + value);
         }
 
         throw new UnsupportedOperationException("Can't convert to color: type=0x"
@@ -480,7 +482,7 @@
         if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
             if (value.type == TypedValue.TYPE_ATTRIBUTE) {
                 throw new UnsupportedOperationException(
-                        "Failed to resolve attribute at index " + index);
+                        "Failed to resolve attribute at index " + index + ": " + value);
             }
             return mResources.loadColorStateList(value, value.resourceId, mTheme);
         }
@@ -516,8 +518,10 @@
                 && type <= TypedValue.TYPE_LAST_INT) {
             return data[index+AssetManager.STYLE_DATA];
         } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+            final TypedValue value = mValue;
+            getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
             throw new UnsupportedOperationException(
-                    "Failed to resolve attribute at index " + index);
+                    "Failed to resolve attribute at index " + index + ": " + value);
         }
 
         throw new UnsupportedOperationException("Can't convert to integer: type=0x"
@@ -560,8 +564,10 @@
             return TypedValue.complexToDimension(
                     data[index + AssetManager.STYLE_DATA], mMetrics);
         } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+            final TypedValue value = mValue;
+            getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
             throw new UnsupportedOperationException(
-                    "Failed to resolve attribute at index " + index);
+                    "Failed to resolve attribute at index " + index + ": " + value);
         }
 
         throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
@@ -605,8 +611,10 @@
             return TypedValue.complexToDimensionPixelOffset(
                     data[index + AssetManager.STYLE_DATA], mMetrics);
         } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+            final TypedValue value = mValue;
+            getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
             throw new UnsupportedOperationException(
-                    "Failed to resolve attribute at index " + index);
+                    "Failed to resolve attribute at index " + index + ": " + value);
         }
 
         throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
@@ -651,8 +659,10 @@
             return TypedValue.complexToDimensionPixelSize(
                 data[index+AssetManager.STYLE_DATA], mMetrics);
         } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+            final TypedValue value = mValue;
+            getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
             throw new UnsupportedOperationException(
-                    "Failed to resolve attribute at index " + index);
+                    "Failed to resolve attribute at index " + index + ": " + value);
         }
 
         throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
@@ -692,8 +702,10 @@
             return TypedValue.complexToDimensionPixelSize(
                 data[index+AssetManager.STYLE_DATA], mMetrics);
         } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+            final TypedValue value = mValue;
+            getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
             throw new UnsupportedOperationException(
-                    "Failed to resolve attribute at index " + index);
+                    "Failed to resolve attribute at index " + index + ": " + value);
         }
 
         throw new UnsupportedOperationException(getPositionDescription()
@@ -765,8 +777,10 @@
             return TypedValue.complexToFraction(
                 data[index+AssetManager.STYLE_DATA], base, pbase);
         } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+            final TypedValue value = mValue;
+            getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
             throw new UnsupportedOperationException(
-                    "Failed to resolve attribute at index " + index);
+                    "Failed to resolve attribute at index " + index + ": " + value);
         }
 
         throw new UnsupportedOperationException("Can't convert to fraction: type=0x"
@@ -853,7 +867,7 @@
         if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
             if (value.type == TypedValue.TYPE_ATTRIBUTE) {
                 throw new UnsupportedOperationException(
-                        "Failed to resolve attribute at index " + index);
+                        "Failed to resolve attribute at index " + index + ": " + value);
             }
             return mResources.loadDrawable(value, value.resourceId, mTheme);
         }
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 87ec06a..a0217c2 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -2493,6 +2493,83 @@
     public static final Key<Integer> SYNC_MAX_LATENCY =
             new Key<Integer>("android.sync.maxLatency", int.class);
 
+    /**
+     * <p>The available depth dataspace stream
+     * configurations that this camera device supports
+     * (i.e. format, width, height, output/input stream).</p>
+     * <p>These are output stream configurations for use with
+     * dataSpace HAL_DATASPACE_DEPTH. The configurations are
+     * listed as <code>(format, width, height, input?)</code> tuples.</p>
+     * <p>Only devices that support depth output for at least
+     * the HAL_PIXEL_FORMAT_Y16 dense depth map may include
+     * this entry.</p>
+     * <p>A device that also supports the HAL_PIXEL_FORMAT_BLOB
+     * sparse depth point cloud must report a single entry for
+     * the format in this list as <code>(HAL_PIXEL_FORMAT_BLOB,
+     * android.depth.maxDepthSamples, 1, OUTPUT)</code> in addition to
+     * the entries for HAL_PIXEL_FORMAT_Y16.</p>
+     * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+     * <p><b>Limited capability</b> -
+     * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+     * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+     *
+     * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+     * @hide
+     */
+    public static final Key<android.hardware.camera2.params.StreamConfiguration[]> DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS =
+            new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.depth.availableDepthStreamConfigurations", android.hardware.camera2.params.StreamConfiguration[].class);
+
+    /**
+     * <p>This lists the minimum frame duration for each
+     * format/size combination for depth output formats.</p>
+     * <p>This should correspond to the frame duration when only that
+     * stream is active, with all processing (typically in android.*.mode)
+     * set to either OFF or FAST.</p>
+     * <p>When multiple streams are used in a request, the minimum frame
+     * duration will be max(individual stream min durations).</p>
+     * <p>The minimum frame duration of a stream (of a particular format, size)
+     * is the same regardless of whether the stream is input or output.</p>
+     * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and
+     * android.scaler.availableStallDurations for more details about
+     * calculating the max frame rate.</p>
+     * <p>(Keep in sync with
+     * StreamConfigurationMap#getOutputMinFrameDuration)</p>
+     * <p><b>Units</b>: (format, width, height, ns) x n</p>
+     * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+     * <p><b>Limited capability</b> -
+     * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+     * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+     *
+     * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+     * @see CaptureRequest#SENSOR_FRAME_DURATION
+     * @hide
+     */
+    public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> DEPTH_AVAILABLE_DEPTH_MIN_FRAME_DURATIONS =
+            new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.depth.availableDepthMinFrameDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+    /**
+     * <p>This lists the maximum stall duration for each
+     * format/size combination for depth streams.</p>
+     * <p>A stall duration is how much extra time would get added
+     * to the normal minimum frame duration for a repeating request
+     * that has streams with non-zero stall.</p>
+     * <p>This functions similarly to
+     * android.scaler.availableStallDurations for depth
+     * streams.</p>
+     * <p>All depth output stream formats may have a nonzero stall
+     * duration.</p>
+     * <p><b>Units</b>: (format, width, height, ns) x n</p>
+     * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+     * <p><b>Limited capability</b> -
+     * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+     * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+     *
+     * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+     * @hide
+     */
+    public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS =
+            new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.depth.availableDepthStallDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
     /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
      * End generated code
      *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
diff --git a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
index 50a58ed..d286d38 100644
--- a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
+++ b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
@@ -66,7 +66,7 @@
     int deleteStream(int streamId);
 
     // non-negative value is the stream ID. negative value is status_t
-    int createStream(int width, int height, int format, in Surface surface);
+    int createStream(in Surface surface);
 
     int createDefaultRequest(int templateId, out CameraMetadataNative request);
 
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index ec450bd1..78aefa5 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -373,9 +373,7 @@
 
                 // Add all new streams
                 for (Surface s : addSet) {
-                    // TODO: remove width,height,format since we are ignoring
-                    // it.
-                    int streamId = mRemoteDevice.createStream(0, 0, 0, s);
+                    int streamId = mRemoteDevice.createStream(s);
                     mConfiguredOutputs.put(streamId, s);
                 }
 
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index fcf172c..26cd498 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -504,7 +504,7 @@
     }
 
     @Override
-    public int createStream(int width, int height, int format, Surface surface) {
+    public int createStream(Surface surface) {
         if (DEBUG) {
             Log.d(TAG, "createStream called.");
         }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 0fee4b3..d96a0e9 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -86,10 +86,10 @@
      */
     public static final int WIFI_SCAN = 6;
 
-     /**
-      * A constant indicating a wifi multicast timer
-      */
-     public static final int WIFI_MULTICAST_ENABLED = 7;
+    /**
+     * A constant indicating a wifi multicast timer
+     */
+    public static final int WIFI_MULTICAST_ENABLED = 7;
 
     /**
      * A constant indicating a video turn on timer
@@ -353,7 +353,9 @@
         public abstract long getWifiRunningTime(long elapsedRealtimeUs, int which);
         public abstract long getFullWifiLockTime(long elapsedRealtimeUs, int which);
         public abstract long getWifiScanTime(long elapsedRealtimeUs, int which);
+        public abstract int getWifiScanCount(int which);
         public abstract long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which);
+        public abstract int getWifiBatchedScanCount(int csphBin, int which);
         public abstract long getWifiMulticastTime(long elapsedRealtimeUs, int which);
         public abstract long getAudioTurnedOnTime(long elapsedRealtimeUs, int which);
         public abstract long getVideoTurnedOnTime(long elapsedRealtimeUs, int which);
@@ -439,14 +441,14 @@
             public abstract boolean isActive();
 
             /**
-             * Returns the total time (in 1/100 sec) spent executing in user code.
+             * Returns the total time (in milliseconds) spent executing in user code.
              *
              * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
              */
             public abstract long getUserTime(int which);
 
             /**
-             * Returns the total time (in 1/100 sec) spent executing in system code.
+             * Returns the total time (in milliseconds) spent executing in system code.
              *
              * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
              */
@@ -474,14 +476,14 @@
             public abstract int getNumAnrs(int which);
 
             /**
-             * Returns the cpu time spent in microseconds while the process was in the foreground.
+             * Returns the cpu time (milliseconds) spent while the process was in the foreground.
              * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
              * @return foreground cpu time in microseconds
              */
             public abstract long getForegroundTime(int which);
 
             /**
-             * Returns the approximate cpu time spent in microseconds, at a certain CPU speed.
+             * Returns the approximate cpu time (in milliseconds) spent at a certain CPU speed.
              * @param speedStep the index of the CPU speed. This is not the actual speed of the
              * CPU.
              * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
@@ -1856,6 +1858,15 @@
     public abstract long getNetworkActivityBytes(int type, int which);
     public abstract long getNetworkActivityPackets(int type, int which);
 
+    public static final int CONTROLLER_IDLE_TIME = 0;
+    public static final int CONTROLLER_RX_TIME = 1;
+    public static final int CONTROLLER_TX_TIME = 2;
+    public static final int CONTROLLER_ENERGY = 3;
+    public static final int NUM_CONTROLLER_ACTIVITY_TYPES = CONTROLLER_ENERGY + 1;
+
+    public abstract long getBluetoothControllerActivity(int type, int which);
+    public abstract long getWifiControllerActivity(int type, int which);
+
     /**
      * Return the wall clock time when battery stats data collection started.
      */
@@ -2140,13 +2151,6 @@
         }
     }
 
-    public final static void formatTime(StringBuilder sb, long time) {
-        long sec = time / 100;
-        formatTimeRaw(sb, sec);
-        sb.append((time - (sec * 100)) * 10);
-        sb.append("ms ");
-    }
-
     public final static void formatTimeMs(StringBuilder sb, long time) {
         long sec = time / 1000;
         formatTimeRaw(sb, sec);
@@ -2572,6 +2576,7 @@
             long wifiPacketsTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which);
             long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which);
             long wifiScanTime = u.getWifiScanTime(rawRealtime, which);
+            int wifiScanCount = u.getWifiScanCount(which);
             long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which);
 
             if (mobileBytesRx > 0 || mobileBytesTx > 0 || wifiBytesRx > 0 || wifiBytesTx > 0
@@ -2584,10 +2589,10 @@
                         mobileActiveTime, mobileActiveCount);
             }
 
-            if (fullWifiLockOnTime != 0 || wifiScanTime != 0
+            if (fullWifiLockOnTime != 0 || wifiScanTime != 0 || wifiScanCount != 0
                     || uidWifiRunningTime != 0) {
                 dumpLine(pw, uid, category, WIFI_DATA,
-                        fullWifiLockOnTime, wifiScanTime, uidWifiRunningTime);
+                        fullWifiLockOnTime, wifiScanTime, uidWifiRunningTime, wifiScanCount);
             }
 
             if (u.hasUserActivity()) {
@@ -2705,9 +2710,9 @@
                         : processStats.entrySet()) {
                     Uid.Proc ps = ent.getValue();
 
-                    final long userMillis = ps.getUserTime(which) * 10;
-                    final long systemMillis = ps.getSystemTime(which) * 10;
-                    final long foregroundMillis = ps.getForegroundTime(which) * 10;
+                    final long userMillis = ps.getUserTime(which);
+                    final long systemMillis = ps.getSystemTime(which);
+                    final long foregroundMillis = ps.getForegroundTime(which);
                     final int starts = ps.getStarts(which);
                     final int numCrashes = ps.getNumCrashes(which);
                     final int numAnrs = ps.getNumAnrs(which);
@@ -3146,6 +3151,35 @@
         if (!didOne) sb.append(" (no activity)");
         pw.println(sb.toString());
 
+        final long wifiIdleTimeMs = getBluetoothControllerActivity(CONTROLLER_IDLE_TIME, which);
+        final long wifiRxTimeMs = getBluetoothControllerActivity(CONTROLLER_RX_TIME, which);
+        final long wifiTxTimeMs = getBluetoothControllerActivity(CONTROLLER_TX_TIME, which);
+        final long wifiTotalTimeMs = wifiIdleTimeMs + wifiRxTimeMs + wifiTxTimeMs;
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  WiFi Idle time: "); formatTimeMs(sb, wifiIdleTimeMs);
+        sb.append(" (");
+        sb.append(formatRatioLocked(wifiIdleTimeMs, wifiTotalTimeMs));
+        sb.append(")");
+        pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  WiFi Rx time:   "); formatTimeMs(sb, wifiRxTimeMs);
+        sb.append(" (");
+        sb.append(formatRatioLocked(wifiRxTimeMs, wifiTotalTimeMs));
+        sb.append(")");
+        pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  WiFi Tx time:   "); formatTimeMs(sb, wifiTxTimeMs);
+        sb.append(" (");
+        sb.append(formatRatioLocked(wifiTxTimeMs, wifiTotalTimeMs));
+        sb.append(")");
+        pw.println(sb.toString());
+
         sb.setLength(0);
         sb.append(prefix);
                 sb.append("  Bluetooth on: "); formatTimeMs(sb, bluetoothOnTime / 1000);
@@ -3173,9 +3207,41 @@
             sb.append(getPhoneDataConnectionCount(i, which));
             sb.append("x");
         }
+
         if (!didOne) sb.append(" (no activity)");
         pw.println(sb.toString());
 
+        final long bluetoothIdleTimeMs =
+                getBluetoothControllerActivity(CONTROLLER_IDLE_TIME, which);
+        final long bluetoothRxTimeMs = getBluetoothControllerActivity(CONTROLLER_RX_TIME, which);
+        final long bluetoothTxTimeMs = getBluetoothControllerActivity(CONTROLLER_TX_TIME, which);
+        final long bluetoothTotalTimeMs = bluetoothIdleTimeMs + bluetoothRxTimeMs +
+                bluetoothTxTimeMs;
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  Bluetooth Idle time: "); formatTimeMs(sb, bluetoothIdleTimeMs);
+        sb.append(" (");
+        sb.append(formatRatioLocked(bluetoothIdleTimeMs, bluetoothTotalTimeMs));
+        sb.append(")");
+        pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  Bluetooth Rx time:   "); formatTimeMs(sb, bluetoothRxTimeMs);
+        sb.append(" (");
+        sb.append(formatRatioLocked(bluetoothRxTimeMs, bluetoothTotalTimeMs));
+        sb.append(")");
+        pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  Bluetooth Tx time:   "); formatTimeMs(sb, bluetoothTxTimeMs);
+        sb.append(" (");
+        sb.append(formatRatioLocked(bluetoothTxTimeMs, bluetoothTotalTimeMs));
+        sb.append(")");
+        pw.println(sb.toString());
+
         pw.println();
 
         if (which == STATS_SINCE_UNPLUGGED) {
@@ -3420,6 +3486,7 @@
             long wifiTxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which);
             long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which);
             long wifiScanTime = u.getWifiScanTime(rawRealtime, which);
+            int wifiScanCount = u.getWifiScanCount(which);
             long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which);
 
             if (mobileRxBytes > 0 || mobileTxBytes > 0
@@ -3455,7 +3522,7 @@
                         pw.print(" received, "); pw.print(wifiTxPackets); pw.println(" sent)");
             }
 
-            if (fullWifiLockOnTime != 0 || wifiScanTime != 0
+            if (fullWifiLockOnTime != 0 || wifiScanTime != 0 || wifiScanCount != 0
                     || uidWifiRunningTime != 0) {
                 sb.setLength(0);
                 sb.append(prefix); sb.append("    Wifi Running: ");
@@ -3469,7 +3536,9 @@
                 sb.append(prefix); sb.append("    Wifi Scan: ");
                         formatTimeMs(sb, wifiScanTime / 1000);
                         sb.append("("); sb.append(formatRatioLocked(wifiScanTime,
-                                whichBatteryRealtime)); sb.append(")");
+                                whichBatteryRealtime)); sb.append(") ");
+                                sb.append(wifiScanCount);
+                                sb.append("x");
                 pw.println(sb.toString());
             }
 
@@ -3728,9 +3797,9 @@
                         sb.append(prefix); sb.append("    Proc ");
                                 sb.append(ent.getKey()); sb.append(":\n");
                         sb.append(prefix); sb.append("      CPU: ");
-                                formatTime(sb, userTime); sb.append("usr + ");
-                                formatTime(sb, systemTime); sb.append("krn ; ");
-                                formatTime(sb, foregroundTime); sb.append("fg");
+                                formatTimeMs(sb, userTime); sb.append("usr + ");
+                                formatTimeMs(sb, systemTime); sb.append("krn ; ");
+                                formatTimeMs(sb, foregroundTime); sb.append("fg");
                         if (starts != 0 || numCrashes != 0 || numAnrs != 0) {
                             sb.append("\n"); sb.append(prefix); sb.append("      ");
                             boolean hasOne = false;
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index 1014573..1a07aee 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -532,10 +532,10 @@
                 InflateException ex = new InflateException(e.getMessage());
                 ex.initCause(e);
                 throw ex;
-            } catch (IOException e) {
+            } catch (Exception e) {
                 InflateException ex = new InflateException(
                         parser.getPositionDescription()
-                        + ": " + e.getMessage());
+                                + ": " + e.getMessage());
                 ex.initCause(e);
                 throw ex;
             } finally {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 8197b3d..0f99e88 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -3326,6 +3326,8 @@
             mContainer.setSplitTouchEnabled(true);
             mContainer.setClippingEnabled(false);
             mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
+            mContainer.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
+            mContainer.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
             mContainer.setContentView(this);
 
             mDrawableLtr = drawableLtr;
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java
index cfeca08..4cd959f 100644
--- a/core/java/com/android/internal/os/BatterySipper.java
+++ b/core/java/com/android/internal/os/BatterySipper.java
@@ -26,12 +26,15 @@
     public double value;
     public double[] values;
     public DrainType drainType;
+
+    // Measured in milliseconds.
     public long usageTime;
     public long cpuTime;
     public long gpsTime;
     public long wifiRunningTime;
     public long cpuFgTime;
     public long wakeLockTime;
+
     public long mobileRxPackets;
     public long mobileTxPackets;
     public long mobileActive;
@@ -48,6 +51,14 @@
     public String[] mPackages;
     public String packageWithHighestDrain;
 
+    // Measured in mAh (milli-ampere per hour).
+    public double wifiPower;
+    public double cpuPower;
+    public double wakeLockPower;
+    public double mobileRadioPower;
+    public double gpsPower;
+    public double sensorPower;
+
     public enum DrainType {
         IDLE,
         CELL,
@@ -107,4 +118,31 @@
         }
         return uidObj.getUid();
     }
+
+    /**
+     * Add stats from other to this BatterySipper.
+     */
+    public void add(BatterySipper other) {
+        cpuTime += other.cpuTime;
+        gpsTime += other.gpsTime;
+        wifiRunningTime += other.wifiRunningTime;
+        cpuFgTime += other.cpuFgTime;
+        wakeLockTime += other.wakeLockTime;
+        mobileRxPackets += other.mobileRxPackets;
+        mobileTxPackets += other.mobileTxPackets;
+        mobileActive += other.mobileActive;
+        mobileActiveCount += other.mobileActiveCount;
+        wifiRxPackets += other.wifiRxPackets;
+        wifiTxPackets += other.wifiTxPackets;
+        mobileRxBytes += other.mobileRxBytes;
+        mobileTxBytes += other.mobileTxBytes;
+        wifiRxBytes += other.wifiRxBytes;
+        wifiTxBytes += other.wifiTxBytes;
+        wifiPower += other.wifiPower;
+        gpsPower += other.gpsPower;
+        cpuPower += other.cpuPower;
+        sensorPower += other.sensorPower;
+        mobileRadioPower += other.mobileRadioPower;
+        wakeLockPower += other.wakeLockPower;
+    }
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index eae4427..d3611bf 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -344,6 +344,7 @@
                 mMobilemsppList.add(bs);
             }
         }
+
         for (int i=0; i<mUserSippers.size(); i++) {
             List<BatterySipper> user = mUserSippers.valueAt(i);
             for (int j=0; j<user.size(); j++) {
@@ -389,8 +390,8 @@
 
     private void processAppUsage(SparseArray<UserHandle> asUsers) {
         final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
-        SensorManager sensorManager = (SensorManager) mContext.getSystemService(
-                Context.SENSOR_SERVICE);
+        final SensorManager sensorManager =
+                (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
         final int which = mStatsType;
         final int speedSteps = mPowerProfile.getNumSpeedSteps();
         final double[] powerCpuNormal = new double[speedSteps];
@@ -401,238 +402,317 @@
         final double mobilePowerPerPacket = getMobilePowerPerPacket();
         final double mobilePowerPerMs = getMobilePowerPerMs();
         final double wifiPowerPerPacket = getWifiPowerPerPacket();
-        long appWakelockTimeUs = 0;
+        long totalAppWakelockTimeUs = 0;
         BatterySipper osApp = null;
         mStatsPeriod = mTypeBatteryRealtime;
-        SparseArray<? extends Uid> uidStats = mStats.getUidStats();
+
+        final ArrayList<BatterySipper> appList = new ArrayList<>();
+
+        // Max values used to normalize later.
+        double maxWifiPower = 0;
+        double maxCpuPower = 0;
+        double maxWakeLockPower = 0;
+        double maxMobileRadioPower = 0;
+        double maxGpsPower = 0;
+        double maxSensorPower = 0;
+
+        final SparseArray<? extends Uid> uidStats = mStats.getUidStats();
         final int NU = uidStats.size();
         for (int iu = 0; iu < NU; iu++) {
-            Uid u = uidStats.valueAt(iu);
-            double p; // in mAs
-            double power = 0; // in mAs
-            double highestDrain = 0;
-            String packageWithHighestDrain = null;
-            Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
-            long cpuTime = 0;
-            long cpuFgTime = 0;
-            long wakelockTime = 0;
-            long gpsTime = 0;
+            final Uid u = uidStats.valueAt(iu);
+            final BatterySipper app = new BatterySipper(
+                    BatterySipper.DrainType.APP, u, new double[]{0});
+
+            final Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
             if (processStats.size() > 0) {
-                // Process CPU time
+                // Process CPU time.
+
+                // Keep track of the package with highest drain.
+                double highestDrain = 0;
+
                 for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
                         : processStats.entrySet()) {
                     Uid.Proc ps = ent.getValue();
-                    final long userTime = ps.getUserTime(which);
-                    final long systemTime = ps.getSystemTime(which);
-                    final long foregroundTime = ps.getForegroundTime(which);
-                    cpuFgTime += foregroundTime * 10; // convert to millis
-                    final long tmpCpuTime = (userTime + systemTime) * 10; // convert to millis
-                    int totalTimeAtSpeeds = 0;
-                    // Get the total first
+                    app.cpuFgTime += ps.getForegroundTime(which);
+                    final long totalCpuTime = ps.getUserTime(which) + ps.getSystemTime(which);
+                    app.cpuTime += totalCpuTime;
+
+                    // Calculate the total CPU time spent at the various speed steps.
+                    long totalTimeAtSpeeds = 0;
                     for (int step = 0; step < speedSteps; step++) {
                         cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which);
                         totalTimeAtSpeeds += cpuSpeedStepTimes[step];
                     }
-                    if (totalTimeAtSpeeds == 0) totalTimeAtSpeeds = 1;
-                    // Then compute the ratio of time spent at each speed
-                    double processPower = 0;
+                    totalTimeAtSpeeds = Math.max(totalTimeAtSpeeds, 1);
+
+                    // Then compute the ratio of time spent at each speed and figure out
+                    // the total power consumption.
+                    double cpuPower = 0;
                     for (int step = 0; step < speedSteps; step++) {
-                        double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds;
-                        if (DEBUG && ratio != 0) Log.d(TAG, "UID " + u.getUid() + ": CPU step #"
-                                + step + " ratio=" + makemAh(ratio) + " power="
-                                + makemAh(ratio*tmpCpuTime*powerCpuNormal[step] / (60*60*1000)));
-                        processPower += ratio * tmpCpuTime * powerCpuNormal[step];
+                        final double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds;
+                        final double cpuSpeedStepPower =
+                                ratio * totalCpuTime * powerCpuNormal[step];
+                        if (DEBUG && ratio != 0) {
+                            Log.d(TAG, "UID " + u.getUid() + ": CPU step #"
+                                    + step + " ratio=" + makemAh(ratio) + " power="
+                                    + makemAh(cpuSpeedStepPower / (60 * 60 * 1000)));
+                        }
+                        cpuPower += cpuSpeedStepPower;
                     }
-                    cpuTime += tmpCpuTime;
-                    if (DEBUG && processPower != 0) {
+
+                    if (DEBUG && cpuPower != 0) {
                         Log.d(TAG, String.format("process %s, cpu power=%s",
-                                ent.getKey(), makemAh(processPower / (60*60*1000))));
+                                ent.getKey(), makemAh(cpuPower / (60 * 60 * 1000))));
                     }
-                    power += processPower;
-                    if (packageWithHighestDrain == null
-                            || packageWithHighestDrain.startsWith("*")) {
-                        highestDrain = processPower;
-                        packageWithHighestDrain = ent.getKey();
-                    } else if (highestDrain < processPower
-                            && !ent.getKey().startsWith("*")) {
-                        highestDrain = processPower;
-                        packageWithHighestDrain = ent.getKey();
+                    app.cpuPower += cpuPower;
+
+                    // Each App can have multiple packages and with multiple running processes.
+                    // Keep track of the package who's process has the highest drain.
+                    if (app.packageWithHighestDrain == null ||
+                            app.packageWithHighestDrain.startsWith("*")) {
+                        highestDrain = cpuPower;
+                        app.packageWithHighestDrain = ent.getKey();
+                    } else if (highestDrain < cpuPower && !ent.getKey().startsWith("*")) {
+                        highestDrain = cpuPower;
+                        app.packageWithHighestDrain = ent.getKey();
                     }
                 }
             }
-            if (cpuFgTime > cpuTime) {
-                if (DEBUG && cpuFgTime > cpuTime + 10000) {
+
+            // Ensure that the CPU times make sense.
+            if (app.cpuFgTime > app.cpuTime) {
+                if (DEBUG && app.cpuFgTime > app.cpuTime + 10000) {
                     Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
                 }
-                cpuTime = cpuFgTime; // Statistics may not have been gathered yet.
+
+                // Statistics may not have been gathered yet.
+                app.cpuTime = app.cpuFgTime;
             }
-            power /= (60*60*1000);
+
+            // Convert the CPU power to mAh
+            app.cpuPower /= (60 * 60 * 1000);
+            maxCpuPower = Math.max(maxCpuPower, app.cpuPower);
 
             // Process wake lock usage
-            Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = u.getWakelockStats();
+            final Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats =
+                    u.getWakelockStats();
+            long wakeLockTimeUs = 0;
             for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> wakelockEntry
                     : wakelockStats.entrySet()) {
-                Uid.Wakelock wakelock = wakelockEntry.getValue();
+                final Uid.Wakelock wakelock = wakelockEntry.getValue();
+
                 // Only care about partial wake locks since full wake locks
                 // are canceled when the user turns the screen off.
                 BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL);
                 if (timer != null) {
-                    wakelockTime += timer.getTotalTimeLocked(mRawRealtime, which);
+                    wakeLockTimeUs += timer.getTotalTimeLocked(mRawRealtime, which);
                 }
             }
-            appWakelockTimeUs += wakelockTime;
-            wakelockTime /= 1000; // convert to millis
+            app.wakeLockTime = wakeLockTimeUs / 1000; // convert to millis
+            totalAppWakelockTimeUs += wakeLockTimeUs;
 
-            // Add cost of holding a wake lock
-            p = (wakelockTime
-                    * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / (60*60*1000);
-            if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wake "
-                    + wakelockTime + " power=" + makemAh(p));
-            power += p;
+            // Add cost of holding a wake lock.
+            app.wakeLockPower = (app.wakeLockTime *
+                    mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / (60 * 60 * 1000);
+            if (DEBUG && app.wakeLockPower != 0) {
+                Log.d(TAG, "UID " + u.getUid() + ": wake "
+                        + app.wakeLockTime + " power=" + makemAh(app.wakeLockPower));
+            }
+            maxWakeLockPower = Math.max(maxWakeLockPower, app.wakeLockPower);
 
-            // Add cost of mobile traffic
-            final long mobileRx = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType);
-            final long mobileTx = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);
-            final long mobileRxB = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, mStatsType);
-            final long mobileTxB = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, mStatsType);
+            // Add cost of mobile traffic.
             final long mobileActive = u.getMobileRadioActiveTime(mStatsType);
+            app.mobileRxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType);
+            app.mobileTxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);
+            app.mobileActive = mobileActive / 1000;
+            app.mobileActiveCount = u.getMobileRadioActiveCount(mStatsType);
+            app.mobileRxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, mStatsType);
+            app.mobileTxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, mStatsType);
+
             if (mobileActive > 0) {
                 // We are tracking when the radio is up, so can use the active time to
                 // determine power use.
                 mAppMobileActive += mobileActive;
-                p = (mobilePowerPerMs * mobileActive) / 1000;
+                app.mobileRadioPower = (mobilePowerPerMs * mobileActive) / 1000;
             } else {
                 // We are not tracking when the radio is up, so must approximate power use
                 // based on the number of packets.
-                p = (mobileRx + mobileTx) * mobilePowerPerPacket;
+                app.mobileRadioPower = (app.mobileRxPackets + app.mobileTxPackets)
+                        * mobilePowerPerPacket;
             }
-            if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
-                    + (mobileRx+mobileTx) + " active time " + mobileActive
-                    + " power=" + makemAh(p));
-            power += p;
+            if (DEBUG && app.mobileRadioPower != 0) {
+                Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
+                        + (app.mobileRxPackets + app.mobileTxPackets)
+                        + " active time " + mobileActive
+                        + " power=" + makemAh(app.mobileRadioPower));
+            }
+            maxMobileRadioPower = Math.max(maxMobileRadioPower, app.mobileRadioPower);
 
             // Add cost of wifi traffic
-            final long wifiRx = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, mStatsType);
-            final long wifiTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, mStatsType);
-            final long wifiRxB = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, mStatsType);
-            final long wifiTxB = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, mStatsType);
-            p = (wifiRx + wifiTx) * wifiPowerPerPacket;
-            if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi packets "
-                    + (mobileRx+mobileTx) + " power=" + makemAh(p));
-            power += p;
+            app.wifiRxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, mStatsType);
+            app.wifiTxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, mStatsType);
+            app.wifiRxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, mStatsType);
+            app.wifiTxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, mStatsType);
 
-            // Add cost of keeping WIFI running.
-            long wifiRunningTimeMs = u.getWifiRunningTime(mRawRealtime, which) / 1000;
-            mAppWifiRunning += wifiRunningTimeMs;
-            p = (wifiRunningTimeMs
-                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60*60*1000);
-            if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi running "
-                    + wifiRunningTimeMs + " power=" + makemAh(p));
-            power += p;
-
-            // Add cost of WIFI scans
-            long wifiScanTimeMs = u.getWifiScanTime(mRawRealtime, which) / 1000;
-            p = (wifiScanTimeMs
-                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN)) / (60*60*1000);
-            if (DEBUG) Log.d(TAG, "UID " + u.getUid() + ": wifi scan " + wifiScanTimeMs
-                    + " power=" + makemAh(p));
-            power += p;
-            for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) {
-                long batchScanTimeMs = u.getWifiBatchedScanTime(bin, mRawRealtime, which) / 1000;
-                p = ((batchScanTimeMs
-                        * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, bin))
-                    ) / (60*60*1000);
-                if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi batched scan # " + bin
-                        + " time=" + batchScanTimeMs + " power=" + makemAh(p));
-                power += p;
+            final double wifiPacketPower = (app.wifiRxPackets + app.wifiTxPackets)
+                    * wifiPowerPerPacket;
+            if (DEBUG && wifiPacketPower != 0) {
+                Log.d(TAG, "UID " + u.getUid() + ": wifi packets "
+                        + (app.wifiRxPackets + app.wifiTxPackets)
+                        + " power=" + makemAh(wifiPacketPower));
             }
 
+            // Add cost of keeping WIFI running.
+            app.wifiRunningTime = u.getWifiRunningTime(mRawRealtime, which) / 1000;
+            mAppWifiRunning += app.wifiRunningTime;
+
+            final double wifiLockPower = (app.wifiRunningTime
+                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60 * 60 * 1000);
+            if (DEBUG && wifiLockPower != 0) {
+                Log.d(TAG, "UID " + u.getUid() + ": wifi running "
+                        + app.wifiRunningTime + " power=" + makemAh(wifiLockPower));
+            }
+
+            // Add cost of WIFI scans
+            final long wifiScanTimeMs = u.getWifiScanTime(mRawRealtime, which) / 1000;
+            final double wifiScanPower = (wifiScanTimeMs
+                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN))
+                    /  (60 * 60 * 1000);
+            if (DEBUG && wifiScanPower != 0) {
+                Log.d(TAG, "UID " + u.getUid() + ": wifi scan " + wifiScanTimeMs
+                        + " power=" + makemAh(wifiScanPower));
+            }
+
+            // Add cost of WIFI batch scans.
+            double wifiBatchScanPower = 0;
+            for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) {
+                final long batchScanTimeMs =
+                        u.getWifiBatchedScanTime(bin, mRawRealtime, which) / 1000;
+                final double batchScanPower = ((batchScanTimeMs
+                        * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, bin))
+                ) / (60 * 60 * 1000);
+                if (DEBUG && batchScanPower != 0) {
+                    Log.d(TAG, "UID " + u.getUid() + ": wifi batched scan # " + bin
+                            + " time=" + batchScanTimeMs + " power=" + makemAh(batchScanPower));
+                }
+                wifiBatchScanPower += batchScanPower;
+            }
+
+            // Add up all the WiFi costs.
+            app.wifiPower = wifiPacketPower + wifiLockPower + wifiScanPower + wifiBatchScanPower;
+            maxWifiPower = Math.max(maxWifiPower, app.wifiPower);
+
             // Process Sensor usage
-            SparseArray<? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
-            int NSE = sensorStats.size();
-            for (int ise=0; ise<NSE; ise++) {
-                Uid.Sensor sensor = sensorStats.valueAt(ise);
-                int sensorHandle = sensorStats.keyAt(ise);
-                BatteryStats.Timer timer = sensor.getSensorTime();
-                long sensorTime = timer.getTotalTimeLocked(mRawRealtime, which) / 1000;
-                double multiplier = 0;
+            final SparseArray<? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
+            final int NSE = sensorStats.size();
+            for (int ise = 0; ise < NSE; ise++) {
+                final Uid.Sensor sensor = sensorStats.valueAt(ise);
+                final int sensorHandle = sensorStats.keyAt(ise);
+                final BatteryStats.Timer timer = sensor.getSensorTime();
+                final long sensorTime = timer.getTotalTimeLocked(mRawRealtime, which) / 1000;
+                double sensorPower = 0;
                 switch (sensorHandle) {
                     case Uid.Sensor.GPS:
-                        multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON);
-                        gpsTime = sensorTime;
+                        app.gpsTime = sensorTime;
+                        app.gpsPower = (app.gpsTime
+                                * mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON))
+                                / (60 * 60 * 1000);
+                        sensorPower = app.gpsPower;
+                        maxGpsPower = Math.max(maxGpsPower, app.gpsPower);
                         break;
                     default:
                         List<Sensor> sensorList = sensorManager.getSensorList(
                                 android.hardware.Sensor.TYPE_ALL);
                         for (android.hardware.Sensor s : sensorList) {
                             if (s.getHandle() == sensorHandle) {
-                                multiplier = s.getPower();
+                                sensorPower = (sensorTime * s.getPower()) / (60 * 60 * 1000);
+                                app.sensorPower += sensorPower;
                                 break;
                             }
                         }
                 }
-                p = (multiplier * sensorTime) / (60*60*1000);
-                if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": sensor #" + sensorHandle
-                        + " time=" + sensorTime + " power=" + makemAh(p));
-                power += p;
+                if (DEBUG && sensorPower != 0) {
+                    Log.d(TAG, "UID " + u.getUid() + ": sensor #" + sensorHandle
+                            + " time=" + sensorTime + " power=" + makemAh(sensorPower));
+                }
+            }
+            maxSensorPower = Math.max(maxSensorPower, app.sensorPower);
+
+            final double totalUnnormalizedPower = app.cpuPower + app.wifiPower + app.wakeLockPower
+                    + app.mobileRadioPower + app.gpsPower + app.sensorPower;
+            if (DEBUG && totalUnnormalizedPower != 0) {
+                Log.d(TAG, String.format("UID %d: total power=%s",
+                        u.getUid(), makemAh(totalUnnormalizedPower)));
             }
 
-            if (DEBUG && power != 0) Log.d(TAG, String.format("UID %d: total power=%s",
-                    u.getUid(), makemAh(power)));
+            // Add the app to the list if it is consuming power.
+            if (totalUnnormalizedPower != 0 || u.getUid() == 0) {
+                appList.add(app);
+            }
+        }
 
-            // Add the app to the list if it is consuming power
-            final int userId = UserHandle.getUserId(u.getUid());
-            if (power != 0 || u.getUid() == 0) {
-                BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u,
-                        new double[] {power});
-                app.cpuTime = cpuTime;
-                app.gpsTime = gpsTime;
-                app.wifiRunningTime = wifiRunningTimeMs;
-                app.cpuFgTime = cpuFgTime;
-                app.wakeLockTime = wakelockTime;
-                app.mobileRxPackets = mobileRx;
-                app.mobileTxPackets = mobileTx;
-                app.mobileActive = mobileActive / 1000;
-                app.mobileActiveCount = u.getMobileRadioActiveCount(mStatsType);
-                app.wifiRxPackets = wifiRx;
-                app.wifiTxPackets = wifiTx;
-                app.mobileRxBytes = mobileRxB;
-                app.mobileTxBytes = mobileTxB;
-                app.wifiRxBytes = wifiRxB;
-                app.wifiTxBytes = wifiTxB;
-                app.packageWithHighestDrain = packageWithHighestDrain;
-                if (u.getUid() == Process.WIFI_UID) {
-                    mWifiSippers.add(app);
-                    mWifiPower += power;
-                } else if (u.getUid() == Process.BLUETOOTH_UID) {
-                    mBluetoothSippers.add(app);
-                    mBluetoothPower += power;
-                } else if (!forAllUsers && asUsers.get(userId) == null
-                        && UserHandle.getAppId(u.getUid()) >= Process.FIRST_APPLICATION_UID) {
-                    List<BatterySipper> list = mUserSippers.get(userId);
-                    if (list == null) {
-                        list = new ArrayList<BatterySipper>();
-                        mUserSippers.put(userId, list);
-                    }
-                    list.add(app);
-                    if (power != 0) {
-                        Double userPower = mUserPower.get(userId);
-                        if (userPower == null) {
-                            userPower = power;
-                        } else {
-                            userPower += power;
-                        }
-                        mUserPower.put(userId, userPower);
-                    }
+        // Fetch real power consumption from hardware.
+        double actualTotalWifiPower = 0.0;
+        if (mStats.getWifiControllerActivity(BatteryStats.CONTROLLER_ENERGY, mStatsType) != 0) {
+            final double kDefaultVoltage = 3.36;
+            final long energy = mStats.getWifiControllerActivity(
+                    BatteryStats.CONTROLLER_ENERGY, mStatsType);
+            final double voltage = mPowerProfile.getAveragePowerOrDefault(
+                    PowerProfile.OPERATING_VOLTAGE_WIFI, kDefaultVoltage);
+            actualTotalWifiPower = energy / (voltage * 1000*60*60);
+        }
+
+        final int appCount = appList.size();
+        for (int i = 0; i < appCount; i++) {
+            // Normalize power where possible.
+            final BatterySipper app = appList.get(i);
+            if (actualTotalWifiPower != 0) {
+                app.wifiPower = (app.wifiPower / maxWifiPower) * actualTotalWifiPower;
+            }
+
+            // Assign the final power consumption here.
+            final double power = app.wifiPower + app.cpuPower + app.wakeLockPower
+                    + app.mobileRadioPower + app.gpsPower + app.sensorPower;
+            app.values[0] = app.value = power;
+
+            //
+            // Add the app to the app list, WiFi, Bluetooth, etc, or into "Other Users" list.
+            //
+
+            final int uid = app.getUid();
+            final int userId = UserHandle.getUserId(uid);
+            if (uid == Process.WIFI_UID) {
+                mWifiSippers.add(app);
+                mWifiPower += power;
+            } else if (uid == Process.BLUETOOTH_UID) {
+                mBluetoothSippers.add(app);
+                mBluetoothPower += power;
+            } else if (!forAllUsers && asUsers.get(userId) == null
+                    && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) {
+                // We are told to just report this user's apps as one large entry.
+                List<BatterySipper> list = mUserSippers.get(userId);
+                if (list == null) {
+                    list = new ArrayList<>();
+                    mUserSippers.put(userId, list);
+                }
+                list.add(app);
+
+                Double userPower = mUserPower.get(userId);
+                if (userPower == null) {
+                    userPower = power;
                 } else {
-                    mUsageList.add(app);
-                    if (power > mMaxPower) mMaxPower = power;
-                    if (power > mMaxRealPower) mMaxRealPower = power;
-                    mComputedPower += power;
+                    userPower += power;
                 }
-                if (u.getUid() == 0) {
-                    osApp = app;
-                }
+                mUserPower.put(userId, userPower);
+            } else {
+                mUsageList.add(app);
+                if (power > mMaxPower) mMaxPower = power;
+                if (power > mMaxRealPower) mMaxRealPower = power;
+                mComputedPower += power;
+            }
+
+            if (uid == 0) {
+                osApp = app;
             }
         }
 
@@ -641,7 +721,7 @@
         // this remainder to the OS, if possible.
         if (osApp != null) {
             long wakeTimeMillis = mBatteryUptime / 1000;
-            wakeTimeMillis -= (appWakelockTimeUs / 1000)
+            wakeTimeMillis -= (totalAppWakelockTimeUs / 1000)
                     + (mStats.getScreenOnTime(mRawRealtime, which) / 1000);
             if (wakeTimeMillis > 0) {
                 double power = (wakeTimeMillis
@@ -741,46 +821,11 @@
         for (int i=0; i<from.size(); i++) {
             BatterySipper wbs = from.get(i);
             if (DEBUG) Log.d(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTime);
-            bs.cpuTime += wbs.cpuTime;
-            bs.gpsTime += wbs.gpsTime;
-            bs.wifiRunningTime += wbs.wifiRunningTime;
-            bs.cpuFgTime += wbs.cpuFgTime;
-            bs.wakeLockTime += wbs.wakeLockTime;
-            bs.mobileRxPackets += wbs.mobileRxPackets;
-            bs.mobileTxPackets += wbs.mobileTxPackets;
-            bs.mobileActive += wbs.mobileActive;
-            bs.mobileActiveCount += wbs.mobileActiveCount;
-            bs.wifiRxPackets += wbs.wifiRxPackets;
-            bs.wifiTxPackets += wbs.wifiTxPackets;
-            bs.mobileRxBytes += wbs.mobileRxBytes;
-            bs.mobileTxBytes += wbs.mobileTxBytes;
-            bs.wifiRxBytes += wbs.wifiRxBytes;
-            bs.wifiTxBytes += wbs.wifiTxBytes;
+            bs.add(wbs);
         }
         bs.computeMobilemspp();
     }
 
-    private void addWiFiUsage() {
-        long onTimeMs = mStats.getWifiOnTime(mRawRealtime, mStatsType) / 1000;
-        long runningTimeMs = mStats.getGlobalWifiRunningTime(mRawRealtime, mStatsType) / 1000;
-        if (DEBUG) Log.d(TAG, "WIFI runningTime=" + runningTimeMs
-                + " app runningTime=" + mAppWifiRunning);
-        runningTimeMs -= mAppWifiRunning;
-        if (runningTimeMs < 0) runningTimeMs = 0;
-        double wifiPower = (onTimeMs * 0 /* TODO */
-                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)
-                + runningTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON))
-                / (60*60*1000);
-        if (DEBUG && wifiPower != 0) {
-            Log.d(TAG, "Wifi: time=" + runningTimeMs + " power=" + makemAh(wifiPower));
-        }
-        if ((wifiPower+mWifiPower) != 0) {
-            BatterySipper bs = addEntry(BatterySipper.DrainType.WIFI, runningTimeMs,
-                    wifiPower + mWifiPower);
-            aggregateSippers(bs, mWifiSippers, "WIFI");
-        }
-    }
-
     private void addIdleUsage() {
         long idleTimeMs = (mTypeBatteryRealtime
                 - mStats.getScreenOnTime(mRawRealtime, mStatsType)) / 1000;
@@ -794,24 +839,81 @@
         }
     }
 
+    /**
+     * We do per-app blaming of WiFi activity. If energy info is reported from the controller,
+     * then only the WiFi process gets blamed here since we normalize power calculations and
+     * assign all the power drain to apps. If energy info is not reported, we attribute the
+     * difference between total running time of WiFi for all apps and the actual running time
+     * of WiFi to the WiFi subsystem.
+     */
+    private void addWiFiUsage() {
+        final long idleTimeMs = mStats.getWifiControllerActivity(
+                BatteryStats.CONTROLLER_IDLE_TIME, mStatsType);
+        final long txTimeMs = mStats.getWifiControllerActivity(
+                BatteryStats.CONTROLLER_TX_TIME, mStatsType);
+        final long rxTimeMs = mStats.getWifiControllerActivity(
+                BatteryStats.CONTROLLER_RX_TIME, mStatsType);
+        final long energy = mStats.getWifiControllerActivity(
+                BatteryStats.CONTROLLER_ENERGY, mStatsType);
+        final long totalTimeRunning = idleTimeMs + txTimeMs + rxTimeMs;
+
+        double powerDrain = 0;
+        if (energy == 0 && totalTimeRunning > 0) {
+            // Energy is not reported, which means we may have left over power drain not attributed
+            // to any app. Assign this power to the WiFi app.
+            // TODO(adamlesinski): This mimics the old behavior. However, mAppWifiRunningTime
+            // is the accumulation of the time each app kept the WiFi chip on. Multiple apps
+            // can do this at the same time, so these times do not add up to the total time
+            // the WiFi chip was on. Consider normalizing the time spent running and calculating
+            // power from that? Normalizing the times will assign a weight to each app which
+            // should better represent power usage.
+            powerDrain = ((totalTimeRunning - mAppWifiRunning)
+                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60*60*1000);
+        }
+
+        if (DEBUG && powerDrain != 0) {
+            Log.d(TAG, "Wifi active: time=" + (txTimeMs + rxTimeMs)
+                    + " power=" + makemAh(powerDrain));
+        }
+
+        // TODO(adamlesinski): mWifiPower is already added as a BatterySipper...
+        // Are we double counting here?
+        final double power = mWifiPower + powerDrain;
+        if (power > 0) {
+            BatterySipper bs = addEntry(BatterySipper.DrainType.WIFI, totalTimeRunning, power);
+            aggregateSippers(bs, mWifiSippers, "WIFI");
+        }
+    }
+
+    /**
+     * Bluetooth usage is not attributed to any apps yet, so the entire blame goes to the
+     * Bluetooth Category.
+     */
     private void addBluetoothUsage() {
-        long btOnTimeMs = mStats.getBluetoothOnTime(mRawRealtime, mStatsType) / 1000;
-        double btPower = btOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_ON)
-                / (60*60*1000);
-        if (DEBUG && btPower != 0) {
-            Log.d(TAG, "Bluetooth: time=" + btOnTimeMs + " power=" + makemAh(btPower));
+        final double kDefaultVoltage = 3.36;
+        final long idleTimeMs = mStats.getBluetoothControllerActivity(
+                BatteryStats.CONTROLLER_IDLE_TIME, mStatsType);
+        final long txTimeMs = mStats.getBluetoothControllerActivity(
+                BatteryStats.CONTROLLER_TX_TIME, mStatsType);
+        final long rxTimeMs = mStats.getBluetoothControllerActivity(
+                BatteryStats.CONTROLLER_RX_TIME, mStatsType);
+        final long energy = mStats.getBluetoothControllerActivity(
+                BatteryStats.CONTROLLER_ENERGY, mStatsType);
+        final double voltage = mPowerProfile.getAveragePowerOrDefault(
+                PowerProfile.OPERATING_VOLTAGE_BLUETOOTH, kDefaultVoltage);
+
+        // energy is measured in mA * V * ms, and we are interested in mAh
+        final double powerDrain = energy / (voltage * 60*60*1000);
+
+        if (DEBUG && powerDrain != 0) {
+            Log.d(TAG, "Bluetooth active: time=" + (txTimeMs + rxTimeMs)
+                    + " power=" + makemAh(powerDrain));
         }
-        int btPingCount = mStats.getBluetoothPingCount();
-        double pingPower = (btPingCount
-                * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_AT_CMD))
-                / (60*60*1000);
-        if (DEBUG && pingPower != 0) {
-            Log.d(TAG, "Bluetooth ping: count=" + btPingCount + " power=" + makemAh(pingPower));
-        }
-        btPower += pingPower;
-        if ((btPower+mBluetoothPower) != 0) {
-            BatterySipper bs = addEntry(BatterySipper.DrainType.BLUETOOTH, btOnTimeMs,
-                    btPower + mBluetoothPower);
+
+        final long totalTime = idleTimeMs + txTimeMs + rxTimeMs;
+        final double power = mBluetoothPower + powerDrain;
+        if (power > 0) {
+            BatterySipper bs = addEntry(BatterySipper.DrainType.BLUETOOTH, totalTime, power);
             aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
         }
     }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index f6c5dc7..f9b1ca1 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -20,11 +20,15 @@
 import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED;
 
 import android.app.ActivityManager;
+import android.bluetooth.BluetoothActivityEnergyInfo;
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.NetworkStats;
+import android.net.wifi.IWifiManager;
+import android.net.wifi.WifiActivityEnergyInfo;
 import android.net.wifi.WifiManager;
 import android.os.BadParcelableException;
 import android.os.BatteryManager;
@@ -38,6 +42,8 @@
 import android.os.ParcelFormatException;
 import android.os.Parcelable;
 import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.WorkSource;
@@ -332,6 +338,12 @@
     final LongSamplingCounter[] mNetworkPacketActivityCounters =
             new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
 
+    final LongSamplingCounter[] mBluetoothActivityCounters =
+            new LongSamplingCounter[NUM_CONTROLLER_ACTIVITY_TYPES];
+
+    final LongSamplingCounter[] mWifiActivityCounters =
+            new LongSamplingCounter[NUM_CONTROLLER_ACTIVITY_TYPES];
+
     boolean mWifiOn;
     StopwatchTimer mWifiOnTimer;
 
@@ -4307,6 +4319,20 @@
         return mBluetoothStateTimer[bluetoothState].getCountLocked(which);
     }
 
+    @Override public long getBluetoothControllerActivity(int type, int which) {
+        if (type >= 0 && type < mBluetoothActivityCounters.length) {
+            return mBluetoothActivityCounters[type].getCountLocked(which);
+        }
+        return 0;
+    }
+
+    @Override public long getWifiControllerActivity(int type, int which) {
+        if (type >= 0 && type < mWifiActivityCounters.length) {
+            return mWifiActivityCounters[type].getCountLocked(which);
+        }
+        return 0;
+    }
+
     @Override public long getFlashlightOnTime(long elapsedRealtimeUs, int which) {
         return mFlashlightOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
     }
@@ -4749,6 +4775,14 @@
         }
 
         @Override
+        public int getWifiScanCount(int which) {
+            if (mWifiScanTimer == null) {
+                return 0;
+            }
+            return mWifiScanTimer.getCountLocked(which);
+        }
+
+        @Override
         public long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which) {
             if (csphBin < 0 || csphBin >= NUM_WIFI_BATCHED_SCAN_BINS) return 0;
             if (mWifiBatchedScanTimer[csphBin] == null) {
@@ -4758,6 +4792,15 @@
         }
 
         @Override
+        public int getWifiBatchedScanCount(int csphBin, int which) {
+            if (csphBin < 0 || csphBin >= NUM_WIFI_BATCHED_SCAN_BINS) return 0;
+            if (mWifiBatchedScanTimer[csphBin] == null) {
+                return 0;
+            }
+            return mWifiBatchedScanTimer[csphBin].getCountLocked(which);
+        }
+
+        @Override
         public long getWifiMulticastTime(long elapsedRealtimeUs, int which) {
             if (mWifiMulticastTimer == null) {
                 return 0;
@@ -5601,17 +5644,17 @@
             boolean mActive = true;
 
             /**
-             * Total time (in 1/100 sec) spent executing in user code.
+             * Total time (in ms) spent executing in user code.
              */
             long mUserTime;
 
             /**
-             * Total time (in 1/100 sec) spent executing in kernel code.
+             * Total time (in ms) spent executing in kernel code.
              */
             long mSystemTime;
 
             /**
-             * Amount of time the process was running in the foreground.
+             * Amount of time (in ms) the process was running in the foreground.
              */
             long mForegroundTime;
 
@@ -6635,6 +6678,10 @@
             mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
             mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
         }
+        for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+            mBluetoothActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
+            mWifiActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
+        }
         mMobileRadioActiveTimer = new StopwatchTimer(null, -400, null, mOnBatteryTimeBase);
         mMobileRadioActivePerAppTimer = new StopwatchTimer(null, -401, null, mOnBatteryTimeBase);
         mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase);
@@ -7222,6 +7269,10 @@
         for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
             mBluetoothStateTimer[i].reset(false);
         }
+        for (int i=0; i< NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+            mBluetoothActivityCounters[i].reset(false);
+            mWifiActivityCounters[i].reset(false);
+        }
         mNumConnectivityChange = mLoadedNumConnectivityChange = mUnpluggedNumConnectivityChange = 0;
 
         for (int i=0; i<mUidStats.size(); i++) {
@@ -7308,6 +7359,9 @@
     public void pullPendingStateUpdatesLocked() {
         updateKernelWakelocksLocked();
         updateNetworkActivityLocked(NET_UPDATE_ALL, SystemClock.elapsedRealtime());
+        // TODO(adamlesinski): enable when bluedroid stops deadlocking. b/19248786
+        // updateBluetoothControllerActivityLocked();
+        updateWifiControllerActivityLocked();
         if (mOnBatteryInternal) {
             final boolean screenOn = mScreenState == Display.STATE_ON;
             updateDischargeScreenLevelsLocked(screenOn, screenOn);
@@ -7744,6 +7798,65 @@
         }
     }
 
+    private void updateBluetoothControllerActivityLocked() {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter == null) {
+            return;
+        }
+
+        // We read the data even if we are not on battery. Each read clears
+        // the previous data, so we must always read to make sure the
+        // data is for the current interval.
+        BluetoothActivityEnergyInfo info = adapter.getControllerActivityEnergyInfo(
+                BluetoothAdapter.ACTIVITY_ENERGY_INFO_REFRESHED);
+        if (info == null || !info.isValid() || !mOnBatteryInternal) {
+            // Bad info or we are not on battery.
+            return;
+        }
+
+        mBluetoothActivityCounters[CONTROLLER_RX_TIME].addCountLocked(
+                info.getControllerRxTimeMillis());
+        mBluetoothActivityCounters[CONTROLLER_TX_TIME].addCountLocked(
+                info.getControllerTxTimeMillis());
+        mBluetoothActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked(
+                info.getControllerIdleTimeMillis());
+        mBluetoothActivityCounters[CONTROLLER_ENERGY].addCountLocked(
+                info.getControllerEnergyUsed());
+    }
+
+    private void updateWifiControllerActivityLocked() {
+        IWifiManager wifiManager = IWifiManager.Stub.asInterface(
+                ServiceManager.getService(Context.WIFI_SERVICE));
+        if (wifiManager == null) {
+            return;
+        }
+
+        WifiActivityEnergyInfo info;
+        try {
+            // We read the data even if we are not on battery. Each read clears
+            // the previous data, so we must always read to make sure the
+            // data is for the current interval.
+            info = wifiManager.reportActivityInfo();
+        } catch (RemoteException e) {
+            // Nothing to report, WiFi is dead.
+            return;
+        }
+
+        if (info == null || !info.isValid() || !mOnBatteryInternal) {
+            // Bad info or we are not on battery.
+            return;
+        }
+
+        mWifiActivityCounters[CONTROLLER_RX_TIME].addCountLocked(
+                info.getControllerRxTimeMillis());
+        mWifiActivityCounters[CONTROLLER_TX_TIME].addCountLocked(
+                info.getControllerTxTimeMillis());
+        mWifiActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked(
+                info.getControllerIdleTimeMillis());
+        mWifiActivityCounters[CONTROLLER_ENERGY].addCountLocked(
+                info.getControllerEnergyUsed());
+    }
+
     public long getAwakeTimeBattery() {
         return computeBatteryUptime(getBatteryUptimeLocked(), STATS_CURRENT);
     }
@@ -8458,6 +8571,15 @@
         for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
             mBluetoothStateTimer[i].readSummaryFromParcelLocked(in);
         }
+
+        for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+            mBluetoothActivityCounters[i].readSummaryFromParcelLocked(in);
+        }
+
+        for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+            mWifiActivityCounters[i].readSummaryFromParcelLocked(in);
+        }
+
         mNumConnectivityChange = mLoadedNumConnectivityChange = in.readInt();
         mFlashlightOn = false;
         mFlashlightOnTimer.readSummaryFromParcelLocked(in);
@@ -8746,6 +8868,12 @@
         for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
             mBluetoothStateTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
         }
+        for (int i=0; i< NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+            mBluetoothActivityCounters[i].writeSummaryFromParcelLocked(out);
+        }
+        for (int i=0; i< NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+            mWifiActivityCounters[i].writeSummaryFromParcelLocked(out);
+        }
         out.writeInt(mNumConnectivityChange);
         mFlashlightOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
 
@@ -9050,6 +9178,15 @@
             mBluetoothStateTimer[i] = new StopwatchTimer(null, -500-i,
                     null, mOnBatteryTimeBase, in);
         }
+
+        for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+            mBluetoothActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+        }
+
+        for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+            mWifiActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+        }
+
         mNumConnectivityChange = in.readInt();
         mLoadedNumConnectivityChange = in.readInt();
         mUnpluggedNumConnectivityChange = in.readInt();
@@ -9195,6 +9332,12 @@
         for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
             mBluetoothStateTimer[i].writeToParcel(out, uSecRealtime);
         }
+        for (int i=0; i< NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+            mBluetoothActivityCounters[i].writeToParcel(out);
+        }
+        for (int i=0; i< NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+            mWifiActivityCounters[i].writeToParcel(out);
+        }
         out.writeInt(mNumConnectivityChange);
         out.writeInt(mLoadedNumConnectivityChange);
         out.writeInt(mUnpluggedNumConnectivityChange);
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index b3bafa1..944eb5a 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -76,6 +76,11 @@
     public static final String POWER_WIFI_ACTIVE = "wifi.active";
 
     /**
+     * Operating voltage of the WiFi controller.
+     */
+    public static final String OPERATING_VOLTAGE_WIFI = "wifi.voltage";
+
+    /**
      * Power consumption when GPS is on.
      */
     public static final String POWER_GPS_ON = "gps.on";
@@ -96,6 +101,11 @@
     public static final String POWER_BLUETOOTH_AT_CMD = "bluetooth.at";
 
     /**
+     * Operating voltage of the Bluetooth controller.
+     */
+    public static final String OPERATING_VOLTAGE_BLUETOOTH = "bluetooth.voltage";
+
+    /**
      * Power consumption when screen is on, not including the backlight power.
      */
     public static final String POWER_SCREEN_ON = "screen.on";
@@ -224,11 +234,13 @@
     }
 
     /**
-     * Returns the average current in mA consumed by the subsystem 
+     * Returns the average current in mA consumed by the subsystem, or the given
+     * default value if the subsystem has no recorded value.
      * @param type the subsystem type
+     * @param defaultValue the value to return if the subsystem has no recorded value.
      * @return the average current in milliAmps.
      */
-    public double getAveragePower(String type) {
+    public double getAveragePowerOrDefault(String type, double defaultValue) {
         if (sPowerMap.containsKey(type)) {
             Object data = sPowerMap.get(type);
             if (data instanceof Double[]) {
@@ -237,9 +249,18 @@
                 return (Double) sPowerMap.get(type);
             }
         } else {
-            return 0;
+            return defaultValue;
         }
     }
+
+    /**
+     * Returns the average current in mA consumed by the subsystem
+     * @param type the subsystem type
+     * @return the average current in milliAmps.
+     */
+    public double getAveragePower(String type) {
+        return getAveragePowerOrDefault(type, 0);
+    }
     
     /**
      * Returns the average current in mA consumed by the subsystem for the given level.
diff --git a/core/java/com/android/internal/os/ProcessCpuTracker.java b/core/java/com/android/internal/os/ProcessCpuTracker.java
index 501e0ec..2983047 100644
--- a/core/java/com/android/internal/os/ProcessCpuTracker.java
+++ b/core/java/com/android/internal/os/ProcessCpuTracker.java
@@ -22,11 +22,13 @@
 import android.os.Process;
 import android.os.StrictMode;
 import android.os.SystemClock;
+import android.system.OsConstants;
 import android.util.Slog;
 
 import com.android.internal.util.FastPrintWriter;
 
 import libcore.io.IoUtils;
+import libcore.io.Libcore;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -130,6 +132,9 @@
 
     private final boolean mIncludeThreads;
 
+    // How long a CPU jiffy is in milliseconds.
+    private final long mJiffyMillis;
+
     private float mLoad1 = 0;
     private float mLoad5 = 0;
     private float mLoad15 = 0;
@@ -269,6 +274,8 @@
 
     public ProcessCpuTracker(boolean includeThreads) {
         mIncludeThreads = includeThreads;
+        long jiffyHz = Libcore.os.sysconf(OsConstants._SC_CLK_TCK);
+        mJiffyMillis = 1000/jiffyHz;
     }
 
     public void onLoadChanged(float load1, float load5, float load15) {
@@ -294,15 +301,15 @@
         if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT,
                 null, sysCpu, null)) {
             // Total user time is user + nice time.
-            final long usertime = sysCpu[0]+sysCpu[1];
+            final long usertime = (sysCpu[0]+sysCpu[1]) * mJiffyMillis;
             // Total system time is simply system time.
-            final long systemtime = sysCpu[2];
+            final long systemtime = sysCpu[2] * mJiffyMillis;
             // Total idle time is simply idle time.
-            final long idletime = sysCpu[3];
+            final long idletime = sysCpu[3] * mJiffyMillis;
             // Total irq time is iowait + irq + softirq time.
-            final long iowaittime = sysCpu[4];
-            final long irqtime = sysCpu[5];
-            final long softirqtime = sysCpu[6];
+            final long iowaittime = sysCpu[4] * mJiffyMillis;
+            final long irqtime = sysCpu[5] * mJiffyMillis;
+            final long softirqtime = sysCpu[6] * mJiffyMillis;
 
             // This code is trying to avoid issues with idle time going backwards,
             // but currently it gets into situations where it triggers most of the time. :(
@@ -318,10 +325,11 @@
                 mRelStatsAreGood = true;
 
                 if (DEBUG) {
-                    Slog.i("Load", "Total U:" + sysCpu[0] + " N:" + sysCpu[1]
-                          + " S:" + sysCpu[2] + " I:" + sysCpu[3]
-                          + " W:" + sysCpu[4] + " Q:" + sysCpu[5]
-                          + " O:" + sysCpu[6]);
+                    Slog.i("Load", "Total U:" + (sysCpu[0]*mJiffyMillis)
+                          + " N:" + (sysCpu[1]*mJiffyMillis)
+                          + " S:" + (sysCpu[2]*mJiffyMillis) + " I:" + (sysCpu[3]*mJiffyMillis)
+                          + " W:" + (sysCpu[4]*mJiffyMillis) + " Q:" + (sysCpu[5]*mJiffyMillis)
+                          + " O:" + (sysCpu[6]*mJiffyMillis));
                     Slog.i("Load", "Rel U:" + mRelUserTime + " S:" + mRelSystemTime
                           + " I:" + mRelIdleTime + " Q:" + mRelIrqTime);
                 }
@@ -414,8 +422,8 @@
 
                     final long minfaults = procStats[PROCESS_STAT_MINOR_FAULTS];
                     final long majfaults = procStats[PROCESS_STAT_MAJOR_FAULTS];
-                    final long utime = procStats[PROCESS_STAT_UTIME];
-                    final long stime = procStats[PROCESS_STAT_STIME];
+                    final long utime = procStats[PROCESS_STAT_UTIME] * mJiffyMillis;
+                    final long stime = procStats[PROCESS_STAT_STIME] * mJiffyMillis;
 
                     if (utime == st.base_utime && stime == st.base_stime) {
                         st.rel_utime = 0;
@@ -489,8 +497,8 @@
                         st.baseName = procStatsString[0];
                         st.base_minfaults = procStats[PROCESS_FULL_STAT_MINOR_FAULTS];
                         st.base_majfaults = procStats[PROCESS_FULL_STAT_MAJOR_FAULTS];
-                        st.base_utime = procStats[PROCESS_FULL_STAT_UTIME];
-                        st.base_stime = procStats[PROCESS_FULL_STAT_STIME];
+                        st.base_utime = procStats[PROCESS_FULL_STAT_UTIME] * mJiffyMillis;
+                        st.base_stime = procStats[PROCESS_FULL_STAT_STIME] * mJiffyMillis;
                     } else {
                         Slog.i(TAG, "Skipping kernel process pid " + pid
                                 + " name " + procStatsString[0]);
@@ -576,7 +584,7 @@
                     null, statsData, null)) {
                 long time = statsData[PROCESS_STAT_UTIME]
                         + statsData[PROCESS_STAT_STIME];
-                return time;
+                return time * mJiffyMillis;
             }
             return 0;
         }
@@ -777,7 +785,7 @@
         for (int i=0; i<N; i++) {
             Stats st = mWorkingProcs.get(i);
             printProcessCPU(pw, st.added ? " +" : (st.removed ? " -": "  "),
-                    st.pid, st.name, (int)(st.rel_uptime+5)/10,
+                    st.pid, st.name, (int)st.rel_uptime,
                     st.rel_utime, st.rel_stime, 0, 0, 0, st.rel_minfaults, st.rel_majfaults);
             if (!st.removed && st.workingThreads != null) {
                 int M = st.workingThreads.size();
@@ -785,7 +793,7 @@
                     Stats tst = st.workingThreads.get(j);
                     printProcessCPU(pw,
                             tst.added ? "   +" : (tst.removed ? "   -": "    "),
-                            tst.pid, tst.name, (int)(st.rel_uptime+5)/10,
+                            tst.pid, tst.name, (int)st.rel_uptime,
                             tst.rel_utime, tst.rel_stime, 0, 0, 0, 0, 0);
                 }
             }
diff --git a/core/java/com/android/internal/widget/PagerAdapter.java b/core/java/com/android/internal/widget/PagerAdapter.java
new file mode 100644
index 0000000..910a720
--- /dev/null
+++ b/core/java/com/android/internal/widget/PagerAdapter.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2015 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.widget;
+
+import android.database.DataSetObservable;
+import android.database.DataSetObserver;
+import android.os.Parcelable;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Base class providing the adapter to populate pages inside of
+ * a {@link android.support.v4.view.ViewPager}.  You will most likely want to use a more
+ * specific implementation of this, such as
+ * {@link android.support.v4.app.FragmentPagerAdapter} or
+ * {@link android.support.v4.app.FragmentStatePagerAdapter}.
+ *
+ * <p>When you implement a PagerAdapter, you must override the following methods
+ * at minimum:</p>
+ * <ul>
+ * <li>{@link #instantiateItem(android.view.ViewGroup, int)}</li>
+ * <li>{@link #destroyItem(android.view.ViewGroup, int, Object)}</li>
+ * <li>{@link #getCount()}</li>
+ * <li>{@link #isViewFromObject(android.view.View, Object)}</li>
+ * </ul>
+ *
+ * <p>PagerAdapter is more general than the adapters used for
+ * {@link android.widget.AdapterView AdapterViews}. Instead of providing a
+ * View recycling mechanism directly ViewPager uses callbacks to indicate the
+ * steps taken during an update. A PagerAdapter may implement a form of View
+ * recycling if desired or use a more sophisticated method of managing page
+ * Views such as Fragment transactions where each page is represented by its
+ * own Fragment.</p>
+ *
+ * <p>ViewPager associates each page with a key Object instead of working with
+ * Views directly. This key is used to track and uniquely identify a given page
+ * independent of its position in the adapter. A call to the PagerAdapter method
+ * {@link #startUpdate(android.view.ViewGroup)} indicates that the contents of the ViewPager
+ * are about to change. One or more calls to {@link #instantiateItem(android.view.ViewGroup, int)}
+ * and/or {@link #destroyItem(android.view.ViewGroup, int, Object)} will follow, and the end
+ * of an update will be signaled by a call to {@link #finishUpdate(android.view.ViewGroup)}.
+ * By the time {@link #finishUpdate(android.view.ViewGroup) finishUpdate} returns the views
+ * associated with the key objects returned by
+ * {@link #instantiateItem(android.view.ViewGroup, int) instantiateItem} should be added to
+ * the parent ViewGroup passed to these methods and the views associated with
+ * the keys passed to {@link #destroyItem(android.view.ViewGroup, int, Object) destroyItem}
+ * should be removed. The method {@link #isViewFromObject(android.view.View, Object)} identifies
+ * whether a page View is associated with a given key object.</p>
+ *
+ * <p>A very simple PagerAdapter may choose to use the page Views themselves
+ * as key objects, returning them from {@link #instantiateItem(android.view.ViewGroup, int)}
+ * after creation and adding them to the parent ViewGroup. A matching
+ * {@link #destroyItem(android.view.ViewGroup, int, Object)} implementation would remove the
+ * View from the parent ViewGroup and {@link #isViewFromObject(android.view.View, Object)}
+ * could be implemented as <code>return view == object;</code>.</p>
+ *
+ * <p>PagerAdapter supports data set changes. Data set changes must occur on the
+ * main thread and must end with a call to {@link #notifyDataSetChanged()} similar
+ * to AdapterView adapters derived from {@link android.widget.BaseAdapter}. A data
+ * set change may involve pages being added, removed, or changing position. The
+ * ViewPager will keep the current page active provided the adapter implements
+ * the method {@link #getItemPosition(Object)}.</p>
+ */
+public abstract class PagerAdapter {
+    private DataSetObservable mObservable = new DataSetObservable();
+
+    public static final int POSITION_UNCHANGED = -1;
+    public static final int POSITION_NONE = -2;
+
+    /**
+     * Return the number of views available.
+     */
+    public abstract int getCount();
+
+    /**
+     * Called when a change in the shown pages is going to start being made.
+     * @param container The containing View which is displaying this adapter's
+     * page views.
+     */
+    public void startUpdate(ViewGroup container) {
+        startUpdate((View) container);
+    }
+
+    /**
+     * Create the page for the given position.  The adapter is responsible
+     * for adding the view to the container given here, although it only
+     * must ensure this is done by the time it returns from
+     * {@link #finishUpdate(android.view.ViewGroup)}.
+     *
+     * @param container The containing View in which the page will be shown.
+     * @param position The page position to be instantiated.
+     * @return Returns an Object representing the new page.  This does not
+     * need to be a View, but can be some other container of the page.
+     */
+    public Object instantiateItem(ViewGroup container, int position) {
+        return instantiateItem((View) container, position);
+    }
+
+    /**
+     * Remove a page for the given position.  The adapter is responsible
+     * for removing the view from its container, although it only must ensure
+     * this is done by the time it returns from {@link #finishUpdate(android.view.ViewGroup)}.
+     *
+     * @param container The containing View from which the page will be removed.
+     * @param position The page position to be removed.
+     * @param object The same object that was returned by
+     * {@link #instantiateItem(android.view.View, int)}.
+     */
+    public void destroyItem(ViewGroup container, int position, Object object) {
+        destroyItem((View) container, position, object);
+    }
+
+    /**
+     * Called to inform the adapter of which item is currently considered to
+     * be the "primary", that is the one show to the user as the current page.
+     *
+     * @param container The containing View from which the page will be removed.
+     * @param position The page position that is now the primary.
+     * @param object The same object that was returned by
+     * {@link #instantiateItem(android.view.View, int)}.
+     */
+    public void setPrimaryItem(ViewGroup container, int position, Object object) {
+        setPrimaryItem((View) container, position, object);
+    }
+
+    /**
+     * Called when the a change in the shown pages has been completed.  At this
+     * point you must ensure that all of the pages have actually been added or
+     * removed from the container as appropriate.
+     * @param container The containing View which is displaying this adapter's
+     * page views.
+     */
+    public void finishUpdate(ViewGroup container) {
+        finishUpdate((View) container);
+    }
+
+    /**
+     * Called when a change in the shown pages is going to start being made.
+     * @param container The containing View which is displaying this adapter's
+     * page views.
+     *
+     * @deprecated Use {@link #startUpdate(android.view.ViewGroup)}
+     */
+    public void startUpdate(View container) {
+    }
+
+    /**
+     * Create the page for the given position.  The adapter is responsible
+     * for adding the view to the container given here, although it only
+     * must ensure this is done by the time it returns from
+     * {@link #finishUpdate(android.view.ViewGroup)}.
+     *
+     * @param container The containing View in which the page will be shown.
+     * @param position The page position to be instantiated.
+     * @return Returns an Object representing the new page.  This does not
+     * need to be a View, but can be some other container of the page.
+     *
+     * @deprecated Use {@link #instantiateItem(android.view.ViewGroup, int)}
+     */
+    public Object instantiateItem(View container, int position) {
+        throw new UnsupportedOperationException(
+                "Required method instantiateItem was not overridden");
+    }
+
+    /**
+     * Remove a page for the given position.  The adapter is responsible
+     * for removing the view from its container, although it only must ensure
+     * this is done by the time it returns from {@link #finishUpdate(android.view.View)}.
+     *
+     * @param container The containing View from which the page will be removed.
+     * @param position The page position to be removed.
+     * @param object The same object that was returned by
+     * {@link #instantiateItem(android.view.View, int)}.
+     *
+     * @deprecated Use {@link #destroyItem(android.view.ViewGroup, int, Object)}
+     */
+    public void destroyItem(View container, int position, Object object) {
+        throw new UnsupportedOperationException("Required method destroyItem was not overridden");
+    }
+
+    /**
+     * Called to inform the adapter of which item is currently considered to
+     * be the "primary", that is the one show to the user as the current page.
+     *
+     * @param container The containing View from which the page will be removed.
+     * @param position The page position that is now the primary.
+     * @param object The same object that was returned by
+     * {@link #instantiateItem(android.view.View, int)}.
+     *
+     * @deprecated Use {@link #setPrimaryItem(android.view.ViewGroup, int, Object)}
+     */
+    public void setPrimaryItem(View container, int position, Object object) {
+    }
+
+    /**
+     * Called when the a change in the shown pages has been completed.  At this
+     * point you must ensure that all of the pages have actually been added or
+     * removed from the container as appropriate.
+     * @param container The containing View which is displaying this adapter's
+     * page views.
+     *
+     * @deprecated Use {@link #finishUpdate(android.view.ViewGroup)}
+     */
+    public void finishUpdate(View container) {
+    }
+
+    /**
+     * Determines whether a page View is associated with a specific key object
+     * as returned by {@link #instantiateItem(android.view.ViewGroup, int)}. This method is
+     * required for a PagerAdapter to function properly.
+     *
+     * @param view Page View to check for association with <code>object</code>
+     * @param object Object to check for association with <code>view</code>
+     * @return true if <code>view</code> is associated with the key object <code>object</code>
+     */
+    public abstract boolean isViewFromObject(View view, Object object);
+
+    /**
+     * Save any instance state associated with this adapter and its pages that should be
+     * restored if the current UI state needs to be reconstructed.
+     *
+     * @return Saved state for this adapter
+     */
+    public Parcelable saveState() {
+        return null;
+    }
+
+    /**
+     * Restore any instance state associated with this adapter and its pages
+     * that was previously saved by {@link #saveState()}.
+     *
+     * @param state State previously saved by a call to {@link #saveState()}
+     * @param loader A ClassLoader that should be used to instantiate any restored objects
+     */
+    public void restoreState(Parcelable state, ClassLoader loader) {
+    }
+
+    /**
+     * Called when the host view is attempting to determine if an item's position
+     * has changed. Returns {@link #POSITION_UNCHANGED} if the position of the given
+     * item has not changed or {@link #POSITION_NONE} if the item is no longer present
+     * in the adapter.
+     *
+     * <p>The default implementation assumes that items will never
+     * change position and always returns {@link #POSITION_UNCHANGED}.
+     *
+     * @param object Object representing an item, previously returned by a call to
+     *               {@link #instantiateItem(android.view.View, int)}.
+     * @return object's new position index from [0, {@link #getCount()}),
+     *         {@link #POSITION_UNCHANGED} if the object's position has not changed,
+     *         or {@link #POSITION_NONE} if the item is no longer present.
+     */
+    public int getItemPosition(Object object) {
+        return POSITION_UNCHANGED;
+    }
+
+    /**
+     * This method should be called by the application if the data backing this adapter has changed
+     * and associated views should update.
+     */
+    public void notifyDataSetChanged() {
+        mObservable.notifyChanged();
+    }
+
+    /**
+     * Register an observer to receive callbacks related to the adapter's data changing.
+     *
+     * @param observer The {@link android.database.DataSetObserver} which will receive callbacks.
+     */
+    public void registerDataSetObserver(DataSetObserver observer) {
+        mObservable.registerObserver(observer);
+    }
+
+    /**
+     * Unregister an observer from callbacks related to the adapter's data changing.
+     *
+     * @param observer The {@link android.database.DataSetObserver} which will be unregistered.
+     */
+    public void unregisterDataSetObserver(DataSetObserver observer) {
+        mObservable.unregisterObserver(observer);
+    }
+
+    /**
+     * This method may be called by the ViewPager to obtain a title string
+     * to describe the specified page. This method may return null
+     * indicating no title for this page. The default implementation returns
+     * null.
+     *
+     * @param position The position of the title requested
+     * @return A title for the requested page
+     */
+    public CharSequence getPageTitle(int position) {
+        return null;
+    }
+
+    /**
+     * Returns the proportional width of a given page as a percentage of the
+     * ViewPager's measured width from (0.f-1.f]
+     *
+     * @param position The position of the page requested
+     * @return Proportional width for the given page position
+     */
+    public float getPageWidth(int position) {
+        return 1.f;
+    }
+}
diff --git a/core/java/com/android/internal/widget/ViewPager.java b/core/java/com/android/internal/widget/ViewPager.java
new file mode 100644
index 0000000..f916e6f
--- /dev/null
+++ b/core/java/com/android/internal/widget/ViewPager.java
@@ -0,0 +1,2866 @@
+/*
+ * Copyright (C) 2015 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.widget;
+
+import android.annotation.DrawableRes;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.FocusFinder;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.SoundEffectConstants;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityRecord;
+import android.view.animation.Interpolator;
+import android.widget.EdgeEffect;
+import android.widget.Scroller;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * Layout manager that allows the user to flip left and right
+ * through pages of data.  You supply an implementation of a
+ * {@link android.support.v4.view.PagerAdapter} to generate the pages that the view shows.
+ *
+ * <p>Note this class is currently under early design and
+ * development.  The API will likely change in later updates of
+ * the compatibility library, requiring changes to the source code
+ * of apps when they are compiled against the newer version.</p>
+ *
+ * <p>ViewPager is most often used in conjunction with {@link android.app.Fragment},
+ * which is a convenient way to supply and manage the lifecycle of each page.
+ * There are standard adapters implemented for using fragments with the ViewPager,
+ * which cover the most common use cases.  These are
+ * {@link android.support.v4.app.FragmentPagerAdapter} and
+ * {@link android.support.v4.app.FragmentStatePagerAdapter}; each of these
+ * classes have simple code showing how to build a full user interface
+ * with them.
+ *
+ * <p>For more information about how to use ViewPager, read <a
+ * href="{@docRoot}training/implementing-navigation/lateral.html">Creating Swipe Views with
+ * Tabs</a>.</p>
+ *
+ * <p>Below is a more complicated example of ViewPager, using it in conjunction
+ * with {@link android.app.ActionBar} tabs.  You can find other examples of using
+ * ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code.
+ *
+ * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java
+ *      complete}
+ */
+public class ViewPager extends ViewGroup {
+    private static final String TAG = "ViewPager";
+    private static final boolean DEBUG = false;
+
+    private static final boolean USE_CACHE = false;
+
+    private static final int DEFAULT_OFFSCREEN_PAGES = 1;
+    private static final int MAX_SETTLE_DURATION = 600; // ms
+    private static final int MIN_DISTANCE_FOR_FLING = 25; // dips
+
+    private static final int DEFAULT_GUTTER_SIZE = 16; // dips
+
+    private static final int MIN_FLING_VELOCITY = 400; // dips
+
+    private static final int[] LAYOUT_ATTRS = new int[] {
+        com.android.internal.R.attr.layout_gravity
+    };
+
+    /**
+     * Used to track what the expected number of items in the adapter should be.
+     * If the app changes this when we don't expect it, we'll throw a big obnoxious exception.
+     */
+    private int mExpectedAdapterCount;
+
+    static class ItemInfo {
+        Object object;
+        int position;
+        boolean scrolling;
+        float widthFactor;
+        float offset;
+    }
+
+    private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>(){
+        @Override
+        public int compare(ItemInfo lhs, ItemInfo rhs) {
+            return lhs.position - rhs.position;
+        }
+    };
+
+    private static final Interpolator sInterpolator = new Interpolator() {
+        public float getInterpolation(float t) {
+            t -= 1.0f;
+            return t * t * t * t * t + 1.0f;
+        }
+    };
+
+    private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
+    private final ItemInfo mTempItem = new ItemInfo();
+
+    private final Rect mTempRect = new Rect();
+
+    private PagerAdapter mAdapter;
+    private int mCurItem;   // Index of currently displayed page.
+    private int mRestoredCurItem = -1;
+    private Parcelable mRestoredAdapterState = null;
+    private ClassLoader mRestoredClassLoader = null;
+    private Scroller mScroller;
+    private PagerObserver mObserver;
+
+    private int mPageMargin;
+    private Drawable mMarginDrawable;
+    private int mTopPageBounds;
+    private int mBottomPageBounds;
+
+    // Offsets of the first and last items, if known.
+    // Set during population, used to determine if we are at the beginning
+    // or end of the pager data set during touch scrolling.
+    private float mFirstOffset = -Float.MAX_VALUE;
+    private float mLastOffset = Float.MAX_VALUE;
+
+    private int mChildWidthMeasureSpec;
+    private int mChildHeightMeasureSpec;
+    private boolean mInLayout;
+
+    private boolean mScrollingCacheEnabled;
+
+    private boolean mPopulatePending;
+    private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
+
+    private boolean mIsBeingDragged;
+    private boolean mIsUnableToDrag;
+    private int mDefaultGutterSize;
+    private int mGutterSize;
+    private int mTouchSlop;
+    /**
+     * Position of the last motion event.
+     */
+    private float mLastMotionX;
+    private float mLastMotionY;
+    private float mInitialMotionX;
+    private float mInitialMotionY;
+    /**
+     * ID of the active pointer. This is used to retain consistency during
+     * drags/flings if multiple pointers are used.
+     */
+    private int mActivePointerId = INVALID_POINTER;
+    /**
+     * Sentinel value for no current active pointer.
+     * Used by {@link #mActivePointerId}.
+     */
+    private static final int INVALID_POINTER = -1;
+
+    /**
+     * Determines speed during touch scrolling
+     */
+    private VelocityTracker mVelocityTracker;
+    private int mMinimumVelocity;
+    private int mMaximumVelocity;
+    private int mFlingDistance;
+    private int mCloseEnough;
+
+    // If the pager is at least this close to its final position, complete the scroll
+    // on touch down and let the user interact with the content inside instead of
+    // "catching" the flinging pager.
+    private static final int CLOSE_ENOUGH = 2; // dp
+
+    private boolean mFakeDragging;
+    private long mFakeDragBeginTime;
+
+    private EdgeEffect mLeftEdge;
+    private EdgeEffect mRightEdge;
+
+    private boolean mFirstLayout = true;
+    private boolean mNeedCalculatePageOffsets = false;
+    private boolean mCalledSuper;
+    private int mDecorChildCount;
+
+    private OnPageChangeListener mOnPageChangeListener;
+    private OnPageChangeListener mInternalPageChangeListener;
+    private OnAdapterChangeListener mAdapterChangeListener;
+    private PageTransformer mPageTransformer;
+
+    private static final int DRAW_ORDER_DEFAULT = 0;
+    private static final int DRAW_ORDER_FORWARD = 1;
+    private static final int DRAW_ORDER_REVERSE = 2;
+    private int mDrawingOrder;
+    private ArrayList<View> mDrawingOrderedChildren;
+    private static final ViewPositionComparator sPositionComparator = new ViewPositionComparator();
+
+    /**
+     * Indicates that the pager is in an idle, settled state. The current page
+     * is fully in view and no animation is in progress.
+     */
+    public static final int SCROLL_STATE_IDLE = 0;
+
+    /**
+     * Indicates that the pager is currently being dragged by the user.
+     */
+    public static final int SCROLL_STATE_DRAGGING = 1;
+
+    /**
+     * Indicates that the pager is in the process of settling to a final position.
+     */
+    public static final int SCROLL_STATE_SETTLING = 2;
+
+    private final Runnable mEndScrollRunnable = new Runnable() {
+        public void run() {
+            setScrollState(SCROLL_STATE_IDLE);
+            populate();
+        }
+    };
+
+    private int mScrollState = SCROLL_STATE_IDLE;
+
+    /**
+     * Callback interface for responding to changing state of the selected page.
+     */
+    public interface OnPageChangeListener {
+
+        /**
+         * This method will be invoked when the current page is scrolled, either as part
+         * of a programmatically initiated smooth scroll or a user initiated touch scroll.
+         *
+         * @param position Position index of the first page currently being displayed.
+         *                 Page position+1 will be visible if positionOffset is nonzero.
+         * @param positionOffset Value from [0, 1) indicating the offset from the page at position.
+         * @param positionOffsetPixels Value in pixels indicating the offset from position.
+         */
+        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
+
+        /**
+         * This method will be invoked when a new page becomes selected. Animation is not
+         * necessarily complete.
+         *
+         * @param position Position index of the new selected page.
+         */
+        public void onPageSelected(int position);
+
+        /**
+         * Called when the scroll state changes. Useful for discovering when the user
+         * begins dragging, when the pager is automatically settling to the current page,
+         * or when it is fully stopped/idle.
+         *
+         * @param state The new scroll state.
+         * @see com.android.internal.widget.ViewPager#SCROLL_STATE_IDLE
+         * @see com.android.internal.widget.ViewPager#SCROLL_STATE_DRAGGING
+         * @see com.android.internal.widget.ViewPager#SCROLL_STATE_SETTLING
+         */
+        public void onPageScrollStateChanged(int state);
+    }
+
+    /**
+     * Simple implementation of the {@link OnPageChangeListener} interface with stub
+     * implementations of each method. Extend this if you do not intend to override
+     * every method of {@link OnPageChangeListener}.
+     */
+    public static class SimpleOnPageChangeListener implements OnPageChangeListener {
+        @Override
+        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+            // This space for rent
+        }
+
+        @Override
+        public void onPageSelected(int position) {
+            // This space for rent
+        }
+
+        @Override
+        public void onPageScrollStateChanged(int state) {
+            // This space for rent
+        }
+    }
+
+    /**
+     * A PageTransformer is invoked whenever a visible/attached page is scrolled.
+     * This offers an opportunity for the application to apply a custom transformation
+     * to the page views using animation properties.
+     *
+     * <p>As property animation is only supported as of Android 3.0 and forward,
+     * setting a PageTransformer on a ViewPager on earlier platform versions will
+     * be ignored.</p>
+     */
+    public interface PageTransformer {
+        /**
+         * Apply a property transformation to the given page.
+         *
+         * @param page Apply the transformation to this page
+         * @param position Position of page relative to the current front-and-center
+         *                 position of the pager. 0 is front and center. 1 is one full
+         *                 page position to the right, and -1 is one page position to the left.
+         */
+        public void transformPage(View page, float position);
+    }
+
+    /**
+     * Used internally to monitor when adapters are switched.
+     */
+    interface OnAdapterChangeListener {
+        public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter);
+    }
+
+    /**
+     * Used internally to tag special types of child views that should be added as
+     * pager decorations by default.
+     */
+    interface Decor {}
+
+    public ViewPager(Context context) {
+        super(context);
+        initViewPager();
+    }
+
+    public ViewPager(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initViewPager();
+    }
+
+    void initViewPager() {
+        setWillNotDraw(false);
+        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
+        setFocusable(true);
+        final Context context = getContext();
+        mScroller = new Scroller(context, sInterpolator);
+        final ViewConfiguration configuration = ViewConfiguration.get(context);
+        final float density = context.getResources().getDisplayMetrics().density;
+
+        mTouchSlop = configuration.getScaledPagingTouchSlop();
+        mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);
+        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+        mLeftEdge = new EdgeEffect(context);
+        mRightEdge = new EdgeEffect(context);
+
+        mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
+        mCloseEnough = (int) (CLOSE_ENOUGH * density);
+        mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);
+
+        setAccessibilityDelegate(new MyAccessibilityDelegate());
+
+        if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        removeCallbacks(mEndScrollRunnable);
+        super.onDetachedFromWindow();
+    }
+
+    private void setScrollState(int newState) {
+        if (mScrollState == newState) {
+            return;
+        }
+
+        mScrollState = newState;
+        if (mPageTransformer != null) {
+            // PageTransformers can do complex things that benefit from hardware layers.
+            enableLayers(newState != SCROLL_STATE_IDLE);
+        }
+        if (mOnPageChangeListener != null) {
+            mOnPageChangeListener.onPageScrollStateChanged(newState);
+        }
+    }
+
+    /**
+     * Set a PagerAdapter that will supply views for this pager as needed.
+     *
+     * @param adapter Adapter to use
+     */
+    public void setAdapter(PagerAdapter adapter) {
+        if (mAdapter != null) {
+            mAdapter.unregisterDataSetObserver(mObserver);
+            mAdapter.startUpdate(this);
+            for (int i = 0; i < mItems.size(); i++) {
+                final ItemInfo ii = mItems.get(i);
+                mAdapter.destroyItem(this, ii.position, ii.object);
+            }
+            mAdapter.finishUpdate(this);
+            mItems.clear();
+            removeNonDecorViews();
+            mCurItem = 0;
+            scrollTo(0, 0);
+        }
+
+        final PagerAdapter oldAdapter = mAdapter;
+        mAdapter = adapter;
+        mExpectedAdapterCount = 0;
+
+        if (mAdapter != null) {
+            if (mObserver == null) {
+                mObserver = new PagerObserver();
+            }
+            mAdapter.registerDataSetObserver(mObserver);
+            mPopulatePending = false;
+            final boolean wasFirstLayout = mFirstLayout;
+            mFirstLayout = true;
+            mExpectedAdapterCount = mAdapter.getCount();
+            if (mRestoredCurItem >= 0) {
+                mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
+                setCurrentItemInternal(mRestoredCurItem, false, true);
+                mRestoredCurItem = -1;
+                mRestoredAdapterState = null;
+                mRestoredClassLoader = null;
+            } else if (!wasFirstLayout) {
+                populate();
+            } else {
+                requestLayout();
+            }
+        }
+
+        if (mAdapterChangeListener != null && oldAdapter != adapter) {
+            mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
+        }
+    }
+
+    private void removeNonDecorViews() {
+        for (int i = 0; i < getChildCount(); i++) {
+            final View child = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            if (!lp.isDecor) {
+                removeViewAt(i);
+                i--;
+            }
+        }
+    }
+
+    /**
+     * Retrieve the current adapter supplying pages.
+     *
+     * @return The currently registered PagerAdapter
+     */
+    public PagerAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    void setOnAdapterChangeListener(OnAdapterChangeListener listener) {
+        mAdapterChangeListener = listener;
+    }
+
+    private int getClientWidth() {
+        return getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
+    }
+
+    /**
+     * Set the currently selected page. If the ViewPager has already been through its first
+     * layout with its current adapter there will be a smooth animated transition between
+     * the current item and the specified item.
+     *
+     * @param item Item index to select
+     */
+    public void setCurrentItem(int item) {
+        mPopulatePending = false;
+        setCurrentItemInternal(item, !mFirstLayout, false);
+    }
+
+    /**
+     * Set the currently selected page.
+     *
+     * @param item Item index to select
+     * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
+     */
+    public void setCurrentItem(int item, boolean smoothScroll) {
+        mPopulatePending = false;
+        setCurrentItemInternal(item, smoothScroll, false);
+    }
+
+    public int getCurrentItem() {
+        return mCurItem;
+    }
+
+    void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
+        setCurrentItemInternal(item, smoothScroll, always, 0);
+    }
+
+    void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
+        if (mAdapter == null || mAdapter.getCount() <= 0) {
+            setScrollingCacheEnabled(false);
+            return;
+        }
+        if (!always && mCurItem == item && mItems.size() != 0) {
+            setScrollingCacheEnabled(false);
+            return;
+        }
+
+        if (item < 0) {
+            item = 0;
+        } else if (item >= mAdapter.getCount()) {
+            item = mAdapter.getCount() - 1;
+        }
+        final int pageLimit = mOffscreenPageLimit;
+        if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
+            // We are doing a jump by more than one page.  To avoid
+            // glitches, we want to keep all current pages in the view
+            // until the scroll ends.
+            for (int i=0; i<mItems.size(); i++) {
+                mItems.get(i).scrolling = true;
+            }
+        }
+        final boolean dispatchSelected = mCurItem != item;
+
+        if (mFirstLayout) {
+            // We don't have any idea how big we are yet and shouldn't have any pages either.
+            // Just set things up and let the pending layout handle things.
+            mCurItem = item;
+            if (dispatchSelected && mOnPageChangeListener != null) {
+                mOnPageChangeListener.onPageSelected(item);
+            }
+            if (dispatchSelected && mInternalPageChangeListener != null) {
+                mInternalPageChangeListener.onPageSelected(item);
+            }
+            requestLayout();
+        } else {
+            populate(item);
+            scrollToItem(item, smoothScroll, velocity, dispatchSelected);
+        }
+    }
+
+    private void scrollToItem(int item, boolean smoothScroll, int velocity,
+            boolean dispatchSelected) {
+        final ItemInfo curInfo = infoForPosition(item);
+        int destX = 0;
+        if (curInfo != null) {
+            final int width = getClientWidth();
+            destX = (int) (width * Math.max(mFirstOffset,
+                    Math.min(curInfo.offset, mLastOffset)));
+        }
+        if (smoothScroll) {
+            smoothScrollTo(destX, 0, velocity);
+            if (dispatchSelected && mOnPageChangeListener != null) {
+                mOnPageChangeListener.onPageSelected(item);
+            }
+            if (dispatchSelected && mInternalPageChangeListener != null) {
+                mInternalPageChangeListener.onPageSelected(item);
+            }
+        } else {
+            if (dispatchSelected && mOnPageChangeListener != null) {
+                mOnPageChangeListener.onPageSelected(item);
+            }
+            if (dispatchSelected && mInternalPageChangeListener != null) {
+                mInternalPageChangeListener.onPageSelected(item);
+            }
+            completeScroll(false);
+            scrollTo(destX, 0);
+            pageScrolled(destX);
+        }
+    }
+
+    /**
+     * Set a listener that will be invoked whenever the page changes or is incrementally
+     * scrolled. See {@link OnPageChangeListener}.
+     *
+     * @param listener Listener to set
+     */
+    public void setOnPageChangeListener(OnPageChangeListener listener) {
+        mOnPageChangeListener = listener;
+    }
+
+    /**
+     * Set a {@link PageTransformer} that will be called for each attached page whenever
+     * the scroll position is changed. This allows the application to apply custom property
+     * transformations to each page, overriding the default sliding look and feel.
+     *
+     * <p><em>Note:</em> Prior to Android 3.0 the property animation APIs did not exist.
+     * As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.</p>
+     *
+     * @param reverseDrawingOrder true if the supplied PageTransformer requires page views
+     *                            to be drawn from last to first instead of first to last.
+     * @param transformer PageTransformer that will modify each page's animation properties
+     */
+    public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) {
+        final boolean hasTransformer = transformer != null;
+        final boolean needsPopulate = hasTransformer != (mPageTransformer != null);
+        mPageTransformer = transformer;
+        setChildrenDrawingOrderEnabled(hasTransformer);
+        if (hasTransformer) {
+            mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD;
+        } else {
+            mDrawingOrder = DRAW_ORDER_DEFAULT;
+        }
+        if (needsPopulate) populate();
+    }
+
+    @Override
+    protected int getChildDrawingOrder(int childCount, int i) {
+        final int index = mDrawingOrder == DRAW_ORDER_REVERSE ? childCount - 1 - i : i;
+        final int result = ((LayoutParams) mDrawingOrderedChildren.get(index).getLayoutParams()).childIndex;
+        return result;
+    }
+
+    /**
+     * Set a separate OnPageChangeListener for internal use by the support library.
+     *
+     * @param listener Listener to set
+     * @return The old listener that was set, if any.
+     */
+    OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) {
+        OnPageChangeListener oldListener = mInternalPageChangeListener;
+        mInternalPageChangeListener = listener;
+        return oldListener;
+    }
+
+    /**
+     * Returns the number of pages that will be retained to either side of the
+     * current page in the view hierarchy in an idle state. Defaults to 1.
+     *
+     * @return How many pages will be kept offscreen on either side
+     * @see #setOffscreenPageLimit(int)
+     */
+    public int getOffscreenPageLimit() {
+        return mOffscreenPageLimit;
+    }
+
+    /**
+     * Set the number of pages that should be retained to either side of the
+     * current page in the view hierarchy in an idle state. Pages beyond this
+     * limit will be recreated from the adapter when needed.
+     *
+     * <p>This is offered as an optimization. If you know in advance the number
+     * of pages you will need to support or have lazy-loading mechanisms in place
+     * on your pages, tweaking this setting can have benefits in perceived smoothness
+     * of paging animations and interaction. If you have a small number of pages (3-4)
+     * that you can keep active all at once, less time will be spent in layout for
+     * newly created view subtrees as the user pages back and forth.</p>
+     *
+     * <p>You should keep this limit low, especially if your pages have complex layouts.
+     * This setting defaults to 1.</p>
+     *
+     * @param limit How many pages will be kept offscreen in an idle state.
+     */
+    public void setOffscreenPageLimit(int limit) {
+        if (limit < DEFAULT_OFFSCREEN_PAGES) {
+            Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
+                    DEFAULT_OFFSCREEN_PAGES);
+            limit = DEFAULT_OFFSCREEN_PAGES;
+        }
+        if (limit != mOffscreenPageLimit) {
+            mOffscreenPageLimit = limit;
+            populate();
+        }
+    }
+
+    /**
+     * Set the margin between pages.
+     *
+     * @param marginPixels Distance between adjacent pages in pixels
+     * @see #getPageMargin()
+     * @see #setPageMarginDrawable(android.graphics.drawable.Drawable)
+     * @see #setPageMarginDrawable(int)
+     */
+    public void setPageMargin(int marginPixels) {
+        final int oldMargin = mPageMargin;
+        mPageMargin = marginPixels;
+
+        final int width = getWidth();
+        recomputeScrollPosition(width, width, marginPixels, oldMargin);
+
+        requestLayout();
+    }
+
+    /**
+     * Return the margin between pages.
+     *
+     * @return The size of the margin in pixels
+     */
+    public int getPageMargin() {
+        return mPageMargin;
+    }
+
+    /**
+     * Set a drawable that will be used to fill the margin between pages.
+     *
+     * @param d Drawable to display between pages
+     */
+    public void setPageMarginDrawable(Drawable d) {
+        mMarginDrawable = d;
+        if (d != null) refreshDrawableState();
+        setWillNotDraw(d == null);
+        invalidate();
+    }
+
+    /**
+     * Set a drawable that will be used to fill the margin between pages.
+     *
+     * @param resId Resource ID of a drawable to display between pages
+     */
+    public void setPageMarginDrawable(@DrawableRes int resId) {
+        setPageMarginDrawable(getContext().getDrawable(resId));
+    }
+
+    @Override
+    protected boolean verifyDrawable(Drawable who) {
+        return super.verifyDrawable(who) || who == mMarginDrawable;
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        final Drawable d = mMarginDrawable;
+        if (d != null && d.isStateful()) {
+            d.setState(getDrawableState());
+        }
+    }
+
+    // We want the duration of the page snap animation to be influenced by the distance that
+    // the screen has to travel, however, we don't want this duration to be effected in a
+    // purely linear fashion. Instead, we use this method to moderate the effect that the distance
+    // of travel has on the overall snap duration.
+    float distanceInfluenceForSnapDuration(float f) {
+        f -= 0.5f; // center the values about 0.
+        f *= 0.3f * Math.PI / 2.0f;
+        return (float) Math.sin(f);
+    }
+
+    /**
+     * Like {@link android.view.View#scrollBy}, but scroll smoothly instead of immediately.
+     *
+     * @param x the number of pixels to scroll by on the X axis
+     * @param y the number of pixels to scroll by on the Y axis
+     */
+    void smoothScrollTo(int x, int y) {
+        smoothScrollTo(x, y, 0);
+    }
+
+    /**
+     * Like {@link android.view.View#scrollBy}, but scroll smoothly instead of immediately.
+     *
+     * @param x the number of pixels to scroll by on the X axis
+     * @param y the number of pixels to scroll by on the Y axis
+     * @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
+     */
+    void smoothScrollTo(int x, int y, int velocity) {
+        if (getChildCount() == 0) {
+            // Nothing to do.
+            setScrollingCacheEnabled(false);
+            return;
+        }
+        int sx = getScrollX();
+        int sy = getScrollY();
+        int dx = x - sx;
+        int dy = y - sy;
+        if (dx == 0 && dy == 0) {
+            completeScroll(false);
+            populate();
+            setScrollState(SCROLL_STATE_IDLE);
+            return;
+        }
+
+        setScrollingCacheEnabled(true);
+        setScrollState(SCROLL_STATE_SETTLING);
+
+        final int width = getClientWidth();
+        final int halfWidth = width / 2;
+        final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
+        final float distance = halfWidth + halfWidth *
+                distanceInfluenceForSnapDuration(distanceRatio);
+
+        int duration = 0;
+        velocity = Math.abs(velocity);
+        if (velocity > 0) {
+            duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
+        } else {
+            final float pageWidth = width * mAdapter.getPageWidth(mCurItem);
+            final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin);
+            duration = (int) ((pageDelta + 1) * 100);
+        }
+        duration = Math.min(duration, MAX_SETTLE_DURATION);
+
+        mScroller.startScroll(sx, sy, dx, dy, duration);
+        postInvalidateOnAnimation();
+    }
+
+    ItemInfo addNewItem(int position, int index) {
+        ItemInfo ii = new ItemInfo();
+        ii.position = position;
+        ii.object = mAdapter.instantiateItem(this, position);
+        ii.widthFactor = mAdapter.getPageWidth(position);
+        if (index < 0 || index >= mItems.size()) {
+            mItems.add(ii);
+        } else {
+            mItems.add(index, ii);
+        }
+        return ii;
+    }
+
+    void dataSetChanged() {
+        // This method only gets called if our observer is attached, so mAdapter is non-null.
+
+        final int adapterCount = mAdapter.getCount();
+        mExpectedAdapterCount = adapterCount;
+        boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
+                mItems.size() < adapterCount;
+        int newCurrItem = mCurItem;
+
+        boolean isUpdating = false;
+        for (int i = 0; i < mItems.size(); i++) {
+            final ItemInfo ii = mItems.get(i);
+            final int newPos = mAdapter.getItemPosition(ii.object);
+
+            if (newPos == PagerAdapter.POSITION_UNCHANGED) {
+                continue;
+            }
+
+            if (newPos == PagerAdapter.POSITION_NONE) {
+                mItems.remove(i);
+                i--;
+
+                if (!isUpdating) {
+                    mAdapter.startUpdate(this);
+                    isUpdating = true;
+                }
+
+                mAdapter.destroyItem(this, ii.position, ii.object);
+                needPopulate = true;
+
+                if (mCurItem == ii.position) {
+                    // Keep the current item in the valid range
+                    newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
+                    needPopulate = true;
+                }
+                continue;
+            }
+
+            if (ii.position != newPos) {
+                if (ii.position == mCurItem) {
+                    // Our current item changed position. Follow it.
+                    newCurrItem = newPos;
+                }
+
+                ii.position = newPos;
+                needPopulate = true;
+            }
+        }
+
+        if (isUpdating) {
+            mAdapter.finishUpdate(this);
+        }
+
+        Collections.sort(mItems, COMPARATOR);
+
+        if (needPopulate) {
+            // Reset our known page widths; populate will recompute them.
+            final int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                final View child = getChildAt(i);
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                if (!lp.isDecor) {
+                    lp.widthFactor = 0.f;
+                }
+            }
+
+            setCurrentItemInternal(newCurrItem, false, true);
+            requestLayout();
+        }
+    }
+
+    void populate() {
+        populate(mCurItem);
+    }
+
+    void populate(int newCurrentItem) {
+        ItemInfo oldCurInfo = null;
+        int focusDirection = View.FOCUS_FORWARD;
+        if (mCurItem != newCurrentItem) {
+            focusDirection = mCurItem < newCurrentItem ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
+            oldCurInfo = infoForPosition(mCurItem);
+            mCurItem = newCurrentItem;
+        }
+
+        if (mAdapter == null) {
+            sortChildDrawingOrder();
+            return;
+        }
+
+        // Bail now if we are waiting to populate.  This is to hold off
+        // on creating views from the time the user releases their finger to
+        // fling to a new position until we have finished the scroll to
+        // that position, avoiding glitches from happening at that point.
+        if (mPopulatePending) {
+            if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
+            sortChildDrawingOrder();
+            return;
+        }
+
+        // Also, don't populate until we are attached to a window.  This is to
+        // avoid trying to populate before we have restored our view hierarchy
+        // state and conflicting with what is restored.
+        if (getWindowToken() == null) {
+            return;
+        }
+
+        mAdapter.startUpdate(this);
+
+        final int pageLimit = mOffscreenPageLimit;
+        final int startPos = Math.max(0, mCurItem - pageLimit);
+        final int N = mAdapter.getCount();
+        final int endPos = Math.min(N-1, mCurItem + pageLimit);
+
+        if (N != mExpectedAdapterCount) {
+            String resName;
+            try {
+                resName = getResources().getResourceName(getId());
+            } catch (Resources.NotFoundException e) {
+                resName = Integer.toHexString(getId());
+            }
+            throw new IllegalStateException("The application's PagerAdapter changed the adapter's" +
+                    " contents without calling PagerAdapter#notifyDataSetChanged!" +
+                    " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N +
+                    " Pager id: " + resName +
+                    " Pager class: " + getClass() +
+                    " Problematic adapter: " + mAdapter.getClass());
+        }
+
+        // Locate the currently focused item or add it if needed.
+        int curIndex = -1;
+        ItemInfo curItem = null;
+        for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
+            final ItemInfo ii = mItems.get(curIndex);
+            if (ii.position >= mCurItem) {
+                if (ii.position == mCurItem) curItem = ii;
+                break;
+            }
+        }
+
+        if (curItem == null && N > 0) {
+            curItem = addNewItem(mCurItem, curIndex);
+        }
+
+        // Fill 3x the available width or up to the number of offscreen
+        // pages requested to either side, whichever is larger.
+        // If we have no current item we have no work to do.
+        if (curItem != null) {
+            float extraWidthLeft = 0.f;
+            int itemIndex = curIndex - 1;
+            ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
+            final int clientWidth = getClientWidth();
+            final float leftWidthNeeded = clientWidth <= 0 ? 0 :
+                    2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
+            for (int pos = mCurItem - 1; pos >= 0; pos--) {
+                if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
+                    if (ii == null) {
+                        break;
+                    }
+                    if (pos == ii.position && !ii.scrolling) {
+                        mItems.remove(itemIndex);
+                        mAdapter.destroyItem(this, pos, ii.object);
+                        if (DEBUG) {
+                            Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
+                                    " view: " + ((View) ii.object));
+                        }
+                        itemIndex--;
+                        curIndex--;
+                        ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
+                    }
+                } else if (ii != null && pos == ii.position) {
+                    extraWidthLeft += ii.widthFactor;
+                    itemIndex--;
+                    ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
+                } else {
+                    ii = addNewItem(pos, itemIndex + 1);
+                    extraWidthLeft += ii.widthFactor;
+                    curIndex++;
+                    ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
+                }
+            }
+
+            float extraWidthRight = curItem.widthFactor;
+            itemIndex = curIndex + 1;
+            if (extraWidthRight < 2.f) {
+                ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
+                final float rightWidthNeeded = clientWidth <= 0 ? 0 :
+                        (float) getPaddingRight() / (float) clientWidth + 2.f;
+                for (int pos = mCurItem + 1; pos < N; pos++) {
+                    if (extraWidthRight >= rightWidthNeeded && pos > endPos) {
+                        if (ii == null) {
+                            break;
+                        }
+                        if (pos == ii.position && !ii.scrolling) {
+                            mItems.remove(itemIndex);
+                            mAdapter.destroyItem(this, pos, ii.object);
+                            if (DEBUG) {
+                                Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
+                                        " view: " + ((View) ii.object));
+                            }
+                            ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
+                        }
+                    } else if (ii != null && pos == ii.position) {
+                        extraWidthRight += ii.widthFactor;
+                        itemIndex++;
+                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
+                    } else {
+                        ii = addNewItem(pos, itemIndex);
+                        itemIndex++;
+                        extraWidthRight += ii.widthFactor;
+                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
+                    }
+                }
+            }
+
+            calculatePageOffsets(curItem, curIndex, oldCurInfo);
+        }
+
+        if (DEBUG) {
+            Log.i(TAG, "Current page list:");
+            for (int i=0; i<mItems.size(); i++) {
+                Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
+            }
+        }
+
+        mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
+
+        mAdapter.finishUpdate(this);
+
+        // Check width measurement of current pages and drawing sort order.
+        // Update LayoutParams as needed.
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            lp.childIndex = i;
+            if (!lp.isDecor && lp.widthFactor == 0.f) {
+                // 0 means requery the adapter for this, it doesn't have a valid width.
+                final ItemInfo ii = infoForChild(child);
+                if (ii != null) {
+                    lp.widthFactor = ii.widthFactor;
+                    lp.position = ii.position;
+                }
+            }
+        }
+        sortChildDrawingOrder();
+
+        if (hasFocus()) {
+            View currentFocused = findFocus();
+            ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
+            if (ii == null || ii.position != mCurItem) {
+                for (int i=0; i<getChildCount(); i++) {
+                    View child = getChildAt(i);
+                    ii = infoForChild(child);
+                    if (ii != null && ii.position == mCurItem) {
+                        if (child.requestFocus(focusDirection)) {
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void sortChildDrawingOrder() {
+        if (mDrawingOrder != DRAW_ORDER_DEFAULT) {
+            if (mDrawingOrderedChildren == null) {
+                mDrawingOrderedChildren = new ArrayList<View>();
+            } else {
+                mDrawingOrderedChildren.clear();
+            }
+            final int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                final View child = getChildAt(i);
+                mDrawingOrderedChildren.add(child);
+            }
+            Collections.sort(mDrawingOrderedChildren, sPositionComparator);
+        }
+    }
+
+    private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
+        final int N = mAdapter.getCount();
+        final int width = getClientWidth();
+        final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
+        // Fix up offsets for later layout.
+        if (oldCurInfo != null) {
+            final int oldCurPosition = oldCurInfo.position;
+            // Base offsets off of oldCurInfo.
+            if (oldCurPosition < curItem.position) {
+                int itemIndex = 0;
+                ItemInfo ii = null;
+                float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset;
+                for (int pos = oldCurPosition + 1;
+                        pos <= curItem.position && itemIndex < mItems.size(); pos++) {
+                    ii = mItems.get(itemIndex);
+                    while (pos > ii.position && itemIndex < mItems.size() - 1) {
+                        itemIndex++;
+                        ii = mItems.get(itemIndex);
+                    }
+                    while (pos < ii.position) {
+                        // We don't have an item populated for this,
+                        // ask the adapter for an offset.
+                        offset += mAdapter.getPageWidth(pos) + marginOffset;
+                        pos++;
+                    }
+                    ii.offset = offset;
+                    offset += ii.widthFactor + marginOffset;
+                }
+            } else if (oldCurPosition > curItem.position) {
+                int itemIndex = mItems.size() - 1;
+                ItemInfo ii = null;
+                float offset = oldCurInfo.offset;
+                for (int pos = oldCurPosition - 1;
+                        pos >= curItem.position && itemIndex >= 0; pos--) {
+                    ii = mItems.get(itemIndex);
+                    while (pos < ii.position && itemIndex > 0) {
+                        itemIndex--;
+                        ii = mItems.get(itemIndex);
+                    }
+                    while (pos > ii.position) {
+                        // We don't have an item populated for this,
+                        // ask the adapter for an offset.
+                        offset -= mAdapter.getPageWidth(pos) + marginOffset;
+                        pos--;
+                    }
+                    offset -= ii.widthFactor + marginOffset;
+                    ii.offset = offset;
+                }
+            }
+        }
+
+        // Base all offsets off of curItem.
+        final int itemCount = mItems.size();
+        float offset = curItem.offset;
+        int pos = curItem.position - 1;
+        mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
+        mLastOffset = curItem.position == N - 1 ?
+                curItem.offset + curItem.widthFactor - 1 : Float.MAX_VALUE;
+        // Previous pages
+        for (int i = curIndex - 1; i >= 0; i--, pos--) {
+            final ItemInfo ii = mItems.get(i);
+            while (pos > ii.position) {
+                offset -= mAdapter.getPageWidth(pos--) + marginOffset;
+            }
+            offset -= ii.widthFactor + marginOffset;
+            ii.offset = offset;
+            if (ii.position == 0) mFirstOffset = offset;
+        }
+        offset = curItem.offset + curItem.widthFactor + marginOffset;
+        pos = curItem.position + 1;
+        // Next pages
+        for (int i = curIndex + 1; i < itemCount; i++, pos++) {
+            final ItemInfo ii = mItems.get(i);
+            while (pos < ii.position) {
+                offset += mAdapter.getPageWidth(pos++) + marginOffset;
+            }
+            if (ii.position == N - 1) {
+                mLastOffset = offset + ii.widthFactor - 1;
+            }
+            ii.offset = offset;
+            offset += ii.widthFactor + marginOffset;
+        }
+
+        mNeedCalculatePageOffsets = false;
+    }
+
+    /**
+     * This is the persistent state that is saved by ViewPager.  Only needed
+     * if you are creating a sublass of ViewPager that must save its own
+     * state, in which case it should implement a subclass of this which
+     * contains that state.
+     */
+    public static class SavedState extends BaseSavedState {
+        int position;
+        Parcelable adapterState;
+        ClassLoader loader;
+
+        public SavedState(Parcel source) {
+            super(source);
+        }
+
+        public SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeInt(position);
+            out.writeParcelable(adapterState, flags);
+        }
+
+        @Override
+        public String toString() {
+            return "FragmentPager.SavedState{"
+                    + Integer.toHexString(System.identityHashCode(this))
+                    + " position=" + position + "}";
+        }
+
+        public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
+            @Override
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+            @Override
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+
+        SavedState(Parcel in, ClassLoader loader) {
+            super(in);
+            if (loader == null) {
+                loader = getClass().getClassLoader();
+            }
+            position = in.readInt();
+            adapterState = in.readParcelable(loader);
+            this.loader = loader;
+        }
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+        SavedState ss = new SavedState(superState);
+        ss.position = mCurItem;
+        if (mAdapter != null) {
+            ss.adapterState = mAdapter.saveState();
+        }
+        return ss;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        if (!(state instanceof SavedState)) {
+            super.onRestoreInstanceState(state);
+            return;
+        }
+
+        SavedState ss = (SavedState)state;
+        super.onRestoreInstanceState(ss.getSuperState());
+
+        if (mAdapter != null) {
+            mAdapter.restoreState(ss.adapterState, ss.loader);
+            setCurrentItemInternal(ss.position, false, true);
+        } else {
+            mRestoredCurItem = ss.position;
+            mRestoredAdapterState = ss.adapterState;
+            mRestoredClassLoader = ss.loader;
+        }
+    }
+
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        if (!checkLayoutParams(params)) {
+            params = generateLayoutParams(params);
+        }
+        final LayoutParams lp = (LayoutParams) params;
+        lp.isDecor |= child instanceof Decor;
+        if (mInLayout) {
+            if (lp != null && lp.isDecor) {
+                throw new IllegalStateException("Cannot add pager decor view during layout");
+            }
+            lp.needsMeasure = true;
+            addViewInLayout(child, index, params);
+        } else {
+            super.addView(child, index, params);
+        }
+
+        if (USE_CACHE) {
+            if (child.getVisibility() != GONE) {
+                child.setDrawingCacheEnabled(mScrollingCacheEnabled);
+            } else {
+                child.setDrawingCacheEnabled(false);
+            }
+        }
+    }
+
+    @Override
+    public void removeView(View view) {
+        if (mInLayout) {
+            removeViewInLayout(view);
+        } else {
+            super.removeView(view);
+        }
+    }
+
+    ItemInfo infoForChild(View child) {
+        for (int i=0; i<mItems.size(); i++) {
+            ItemInfo ii = mItems.get(i);
+            if (mAdapter.isViewFromObject(child, ii.object)) {
+                return ii;
+            }
+        }
+        return null;
+    }
+
+    ItemInfo infoForAnyChild(View child) {
+        ViewParent parent;
+        while ((parent=child.getParent()) != this) {
+            if (parent == null || !(parent instanceof View)) {
+                return null;
+            }
+            child = (View)parent;
+        }
+        return infoForChild(child);
+    }
+
+    ItemInfo infoForPosition(int position) {
+        for (int i = 0; i < mItems.size(); i++) {
+            ItemInfo ii = mItems.get(i);
+            if (ii.position == position) {
+                return ii;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mFirstLayout = true;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // For simple implementation, our internal size is always 0.
+        // We depend on the container to specify the layout size of
+        // our view.  We can't really know what it is since we will be
+        // adding and removing different arbitrary views and do not
+        // want the layout to change as this happens.
+        setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
+                getDefaultSize(0, heightMeasureSpec));
+
+        final int measuredWidth = getMeasuredWidth();
+        final int maxGutterSize = measuredWidth / 10;
+        mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);
+
+        // Children are just made to fill our space.
+        int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
+        int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
+
+        /*
+         * Make sure all children have been properly measured. Decor views first.
+         * Right now we cheat and make this less complicated by assuming decor
+         * views won't intersect. We will pin to edges based on gravity.
+         */
+        int size = getChildCount();
+        for (int i = 0; i < size; ++i) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                if (lp != null && lp.isDecor) {
+                    final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+                    final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
+                    int widthMode = MeasureSpec.AT_MOST;
+                    int heightMode = MeasureSpec.AT_MOST;
+                    boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
+                    boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;
+
+                    if (consumeVertical) {
+                        widthMode = MeasureSpec.EXACTLY;
+                    } else if (consumeHorizontal) {
+                        heightMode = MeasureSpec.EXACTLY;
+                    }
+
+                    int widthSize = childWidthSize;
+                    int heightSize = childHeightSize;
+                    if (lp.width != LayoutParams.WRAP_CONTENT) {
+                        widthMode = MeasureSpec.EXACTLY;
+                        if (lp.width != LayoutParams.FILL_PARENT) {
+                            widthSize = lp.width;
+                        }
+                    }
+                    if (lp.height != LayoutParams.WRAP_CONTENT) {
+                        heightMode = MeasureSpec.EXACTLY;
+                        if (lp.height != LayoutParams.FILL_PARENT) {
+                            heightSize = lp.height;
+                        }
+                    }
+                    final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
+                    final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
+                    child.measure(widthSpec, heightSpec);
+
+                    if (consumeVertical) {
+                        childHeightSize -= child.getMeasuredHeight();
+                    } else if (consumeHorizontal) {
+                        childWidthSize -= child.getMeasuredWidth();
+                    }
+                }
+            }
+        }
+
+        mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
+        mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);
+
+        // Make sure we have created all fragments that we need to have shown.
+        mInLayout = true;
+        populate();
+        mInLayout = false;
+
+        // Page views next.
+        size = getChildCount();
+        for (int i = 0; i < size; ++i) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
+                        + ": " + mChildWidthMeasureSpec);
+
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                if (lp == null || !lp.isDecor) {
+                    final int widthSpec = MeasureSpec.makeMeasureSpec(
+                            (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
+                    child.measure(widthSpec, mChildHeightMeasureSpec);
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        // Make sure scroll position is set correctly.
+        if (w != oldw) {
+            recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin);
+        }
+    }
+
+    private void recomputeScrollPosition(int width, int oldWidth, int margin, int oldMargin) {
+        if (oldWidth > 0 && !mItems.isEmpty()) {
+            final int widthWithMargin = width - getPaddingLeft() - getPaddingRight() + margin;
+            final int oldWidthWithMargin = oldWidth - getPaddingLeft() - getPaddingRight()
+                                           + oldMargin;
+            final int xpos = getScrollX();
+            final float pageOffset = (float) xpos / oldWidthWithMargin;
+            final int newOffsetPixels = (int) (pageOffset * widthWithMargin);
+
+            scrollTo(newOffsetPixels, getScrollY());
+            if (!mScroller.isFinished()) {
+                // We now return to your regularly scheduled scroll, already in progress.
+                final int newDuration = mScroller.getDuration() - mScroller.timePassed();
+                ItemInfo targetInfo = infoForPosition(mCurItem);
+                mScroller.startScroll(newOffsetPixels, 0,
+                        (int) (targetInfo.offset * width), 0, newDuration);
+            }
+        } else {
+            final ItemInfo ii = infoForPosition(mCurItem);
+            final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;
+            final int scrollPos = (int) (scrollOffset *
+                                         (width - getPaddingLeft() - getPaddingRight()));
+            if (scrollPos != getScrollX()) {
+                completeScroll(false);
+                scrollTo(scrollPos, getScrollY());
+            }
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        final int count = getChildCount();
+        int width = r - l;
+        int height = b - t;
+        int paddingLeft = getPaddingLeft();
+        int paddingTop = getPaddingTop();
+        int paddingRight = getPaddingRight();
+        int paddingBottom = getPaddingBottom();
+        final int scrollX = getScrollX();
+
+        int decorCount = 0;
+
+        // First pass - decor views. We need to do this in two passes so that
+        // we have the proper offsets for non-decor views later.
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                int childLeft = 0;
+                int childTop = 0;
+                if (lp.isDecor) {
+                    final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+                    final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
+                    switch (hgrav) {
+                        default:
+                            childLeft = paddingLeft;
+                            break;
+                        case Gravity.LEFT:
+                            childLeft = paddingLeft;
+                            paddingLeft += child.getMeasuredWidth();
+                            break;
+                        case Gravity.CENTER_HORIZONTAL:
+                            childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
+                                    paddingLeft);
+                            break;
+                        case Gravity.RIGHT:
+                            childLeft = width - paddingRight - child.getMeasuredWidth();
+                            paddingRight += child.getMeasuredWidth();
+                            break;
+                    }
+                    switch (vgrav) {
+                        default:
+                            childTop = paddingTop;
+                            break;
+                        case Gravity.TOP:
+                            childTop = paddingTop;
+                            paddingTop += child.getMeasuredHeight();
+                            break;
+                        case Gravity.CENTER_VERTICAL:
+                            childTop = Math.max((height - child.getMeasuredHeight()) / 2,
+                                    paddingTop);
+                            break;
+                        case Gravity.BOTTOM:
+                            childTop = height - paddingBottom - child.getMeasuredHeight();
+                            paddingBottom += child.getMeasuredHeight();
+                            break;
+                    }
+                    childLeft += scrollX;
+                    child.layout(childLeft, childTop,
+                            childLeft + child.getMeasuredWidth(),
+                            childTop + child.getMeasuredHeight());
+                    decorCount++;
+                }
+            }
+        }
+
+        final int childWidth = width - paddingLeft - paddingRight;
+        // Page views. Do this once we have the right padding offsets from above.
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                ItemInfo ii;
+                if (!lp.isDecor && (ii = infoForChild(child)) != null) {
+                    int loff = (int) (childWidth * ii.offset);
+                    int childLeft = paddingLeft + loff;
+                    int childTop = paddingTop;
+                    if (lp.needsMeasure) {
+                        // This was added during layout and needs measurement.
+                        // Do it now that we know what we're working with.
+                        lp.needsMeasure = false;
+                        final int widthSpec = MeasureSpec.makeMeasureSpec(
+                                (int) (childWidth * lp.widthFactor),
+                                MeasureSpec.EXACTLY);
+                        final int heightSpec = MeasureSpec.makeMeasureSpec(
+                                (int) (height - paddingTop - paddingBottom),
+                                MeasureSpec.EXACTLY);
+                        child.measure(widthSpec, heightSpec);
+                    }
+                    if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
+                            + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
+                            + "x" + child.getMeasuredHeight());
+                    child.layout(childLeft, childTop,
+                            childLeft + child.getMeasuredWidth(),
+                            childTop + child.getMeasuredHeight());
+                }
+            }
+        }
+        mTopPageBounds = paddingTop;
+        mBottomPageBounds = height - paddingBottom;
+        mDecorChildCount = decorCount;
+
+        if (mFirstLayout) {
+            scrollToItem(mCurItem, false, 0, false);
+        }
+        mFirstLayout = false;
+    }
+
+    @Override
+    public void computeScroll() {
+        if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
+            int oldX = getScrollX();
+            int oldY = getScrollY();
+            int x = mScroller.getCurrX();
+            int y = mScroller.getCurrY();
+
+            if (oldX != x || oldY != y) {
+                scrollTo(x, y);
+                if (!pageScrolled(x)) {
+                    mScroller.abortAnimation();
+                    scrollTo(0, y);
+                }
+            }
+
+            // Keep on drawing until the animation has finished.
+            postInvalidateOnAnimation();
+            return;
+        }
+
+        // Done with scroll, clean up state.
+        completeScroll(true);
+    }
+
+    private boolean pageScrolled(int xpos) {
+        if (mItems.size() == 0) {
+            mCalledSuper = false;
+            onPageScrolled(0, 0, 0);
+            if (!mCalledSuper) {
+                throw new IllegalStateException(
+                        "onPageScrolled did not call superclass implementation");
+            }
+            return false;
+        }
+        final ItemInfo ii = infoForCurrentScrollPosition();
+        final int width = getClientWidth();
+        final int widthWithMargin = width + mPageMargin;
+        final float marginOffset = (float) mPageMargin / width;
+        final int currentPage = ii.position;
+        final float pageOffset = (((float) xpos / width) - ii.offset) /
+                (ii.widthFactor + marginOffset);
+        final int offsetPixels = (int) (pageOffset * widthWithMargin);
+
+        mCalledSuper = false;
+        onPageScrolled(currentPage, pageOffset, offsetPixels);
+        if (!mCalledSuper) {
+            throw new IllegalStateException(
+                    "onPageScrolled did not call superclass implementation");
+        }
+        return true;
+    }
+
+    /**
+     * This method will be invoked when the current page is scrolled, either as part
+     * of a programmatically initiated smooth scroll or a user initiated touch scroll.
+     * If you override this method you must call through to the superclass implementation
+     * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled
+     * returns.
+     *
+     * @param position Position index of the first page currently being displayed.
+     *                 Page position+1 will be visible if positionOffset is nonzero.
+     * @param offset Value from [0, 1) indicating the offset from the page at position.
+     * @param offsetPixels Value in pixels indicating the offset from position.
+     */
+    protected void onPageScrolled(int position, float offset, int offsetPixels) {
+        // Offset any decor views if needed - keep them on-screen at all times.
+        if (mDecorChildCount > 0) {
+            final int scrollX = getScrollX();
+            int paddingLeft = getPaddingLeft();
+            int paddingRight = getPaddingRight();
+            final int width = getWidth();
+            final int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                final View child = getChildAt(i);
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                if (!lp.isDecor) continue;
+
+                final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+                int childLeft = 0;
+                switch (hgrav) {
+                    default:
+                        childLeft = paddingLeft;
+                        break;
+                    case Gravity.LEFT:
+                        childLeft = paddingLeft;
+                        paddingLeft += child.getWidth();
+                        break;
+                    case Gravity.CENTER_HORIZONTAL:
+                        childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
+                                paddingLeft);
+                        break;
+                    case Gravity.RIGHT:
+                        childLeft = width - paddingRight - child.getMeasuredWidth();
+                        paddingRight += child.getMeasuredWidth();
+                        break;
+                }
+                childLeft += scrollX;
+
+                final int childOffset = childLeft - child.getLeft();
+                if (childOffset != 0) {
+                    child.offsetLeftAndRight(childOffset);
+                }
+            }
+        }
+
+        if (mOnPageChangeListener != null) {
+            mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
+        }
+        if (mInternalPageChangeListener != null) {
+            mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);
+        }
+
+        if (mPageTransformer != null) {
+            final int scrollX = getScrollX();
+            final int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                final View child = getChildAt(i);
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+                if (lp.isDecor) continue;
+
+                final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth();
+                mPageTransformer.transformPage(child, transformPos);
+            }
+        }
+
+        mCalledSuper = true;
+    }
+
+    private void completeScroll(boolean postEvents) {
+        boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING;
+        if (needPopulate) {
+            // Done with scroll, no longer want to cache view drawing.
+            setScrollingCacheEnabled(false);
+            mScroller.abortAnimation();
+            int oldX = getScrollX();
+            int oldY = getScrollY();
+            int x = mScroller.getCurrX();
+            int y = mScroller.getCurrY();
+            if (oldX != x || oldY != y) {
+                scrollTo(x, y);
+            }
+        }
+        mPopulatePending = false;
+        for (int i=0; i<mItems.size(); i++) {
+            ItemInfo ii = mItems.get(i);
+            if (ii.scrolling) {
+                needPopulate = true;
+                ii.scrolling = false;
+            }
+        }
+        if (needPopulate) {
+            if (postEvents) {
+                postOnAnimation(mEndScrollRunnable);
+            } else {
+                mEndScrollRunnable.run();
+            }
+        }
+    }
+
+    private boolean isGutterDrag(float x, float dx) {
+        return (x < mGutterSize && dx > 0) || (x > getWidth() - mGutterSize && dx < 0);
+    }
+
+    private void enableLayers(boolean enable) {
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final int layerType = enable ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE;
+            getChildAt(i).setLayerType(layerType, null);
+        }
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        /*
+         * This method JUST determines whether we want to intercept the motion.
+         * If we return true, onMotionEvent will be called and we do the actual
+         * scrolling there.
+         */
+
+        final int action = ev.getAction() & MotionEvent.ACTION_MASK;
+
+        // Always take care of the touch gesture being complete.
+        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+            // Release the drag.
+            if (DEBUG) Log.v(TAG, "Intercept done!");
+            mIsBeingDragged = false;
+            mIsUnableToDrag = false;
+            mActivePointerId = INVALID_POINTER;
+            if (mVelocityTracker != null) {
+                mVelocityTracker.recycle();
+                mVelocityTracker = null;
+            }
+            return false;
+        }
+
+        // Nothing more to do here if we have decided whether or not we
+        // are dragging.
+        if (action != MotionEvent.ACTION_DOWN) {
+            if (mIsBeingDragged) {
+                if (DEBUG) Log.v(TAG, "Intercept returning true!");
+                return true;
+            }
+            if (mIsUnableToDrag) {
+                if (DEBUG) Log.v(TAG, "Intercept returning false!");
+                return false;
+            }
+        }
+
+        switch (action) {
+            case MotionEvent.ACTION_MOVE: {
+                /*
+                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
+                 * whether the user has moved far enough from his original down touch.
+                 */
+
+                /*
+                * Locally do absolute value. mLastMotionY is set to the y value
+                * of the down event.
+                */
+                final int activePointerId = mActivePointerId;
+                if (activePointerId == INVALID_POINTER) {
+                    // If we don't have a valid id, the touch down wasn't on content.
+                    break;
+                }
+
+                final int pointerIndex = ev.findPointerIndex(activePointerId);
+                final float x = ev.getX(pointerIndex);
+                final float dx = x - mLastMotionX;
+                final float xDiff = Math.abs(dx);
+                final float y = ev.getY(pointerIndex);
+                final float yDiff = Math.abs(y - mInitialMotionY);
+                if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
+
+                if (dx != 0 && !isGutterDrag(mLastMotionX, dx) &&
+                        canScroll(this, false, (int) dx, (int) x, (int) y)) {
+                    // Nested view has scrollable area under this point. Let it be handled there.
+                    mLastMotionX = x;
+                    mLastMotionY = y;
+                    mIsUnableToDrag = true;
+                    return false;
+                }
+                if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
+                    if (DEBUG) Log.v(TAG, "Starting drag!");
+                    mIsBeingDragged = true;
+                    requestParentDisallowInterceptTouchEvent(true);
+                    setScrollState(SCROLL_STATE_DRAGGING);
+                    mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :
+                            mInitialMotionX - mTouchSlop;
+                    mLastMotionY = y;
+                    setScrollingCacheEnabled(true);
+                } else if (yDiff > mTouchSlop) {
+                    // The finger has moved enough in the vertical
+                    // direction to be counted as a drag...  abort
+                    // any attempt to drag horizontally, to work correctly
+                    // with children that have scrolling containers.
+                    if (DEBUG) Log.v(TAG, "Starting unable to drag!");
+                    mIsUnableToDrag = true;
+                }
+                if (mIsBeingDragged) {
+                    // Scroll to follow the motion event
+                    if (performDrag(x)) {
+                        postInvalidateOnAnimation();
+                    }
+                }
+                break;
+            }
+
+            case MotionEvent.ACTION_DOWN: {
+                /*
+                 * Remember location of down touch.
+                 * ACTION_DOWN always refers to pointer index 0.
+                 */
+                mLastMotionX = mInitialMotionX = ev.getX();
+                mLastMotionY = mInitialMotionY = ev.getY();
+                mActivePointerId = ev.getPointerId(0);
+                mIsUnableToDrag = false;
+
+                mScroller.computeScrollOffset();
+                if (mScrollState == SCROLL_STATE_SETTLING &&
+                        Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
+                    // Let the user 'catch' the pager as it animates.
+                    mScroller.abortAnimation();
+                    mPopulatePending = false;
+                    populate();
+                    mIsBeingDragged = true;
+                    requestParentDisallowInterceptTouchEvent(true);
+                    setScrollState(SCROLL_STATE_DRAGGING);
+                } else {
+                    completeScroll(false);
+                    mIsBeingDragged = false;
+                }
+
+                if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
+                        + " mIsBeingDragged=" + mIsBeingDragged
+                        + "mIsUnableToDrag=" + mIsUnableToDrag);
+                break;
+            }
+
+            case MotionEvent.ACTION_POINTER_UP:
+                onSecondaryPointerUp(ev);
+                break;
+        }
+
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(ev);
+
+        /*
+         * The only time we want to intercept motion events is if we are in the
+         * drag mode.
+         */
+        return mIsBeingDragged;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (mFakeDragging) {
+            // A fake drag is in progress already, ignore this real one
+            // but still eat the touch events.
+            // (It is likely that the user is multi-touching the screen.)
+            return true;
+        }
+
+        if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
+            // Don't handle edge touches immediately -- they may actually belong to one of our
+            // descendants.
+            return false;
+        }
+
+        if (mAdapter == null || mAdapter.getCount() == 0) {
+            // Nothing to present or scroll; nothing to touch.
+            return false;
+        }
+
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(ev);
+
+        final int action = ev.getAction();
+        boolean needsInvalidate = false;
+
+        switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_DOWN: {
+                mScroller.abortAnimation();
+                mPopulatePending = false;
+                populate();
+
+                // Remember where the motion event started
+                mLastMotionX = mInitialMotionX = ev.getX();
+                mLastMotionY = mInitialMotionY = ev.getY();
+                mActivePointerId = ev.getPointerId(0);
+                break;
+            }
+            case MotionEvent.ACTION_MOVE:
+                if (!mIsBeingDragged) {
+                    final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+                    final float x = ev.getX(pointerIndex);
+                    final float xDiff = Math.abs(x - mLastMotionX);
+                    final float y = ev.getY(pointerIndex);
+                    final float yDiff = Math.abs(y - mLastMotionY);
+                    if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
+                    if (xDiff > mTouchSlop && xDiff > yDiff) {
+                        if (DEBUG) Log.v(TAG, "Starting drag!");
+                        mIsBeingDragged = true;
+                        requestParentDisallowInterceptTouchEvent(true);
+                        mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
+                                mInitialMotionX - mTouchSlop;
+                        mLastMotionY = y;
+                        setScrollState(SCROLL_STATE_DRAGGING);
+                        setScrollingCacheEnabled(true);
+
+                        // Disallow Parent Intercept, just in case
+                        ViewParent parent = getParent();
+                        if (parent != null) {
+                            parent.requestDisallowInterceptTouchEvent(true);
+                        }
+                    }
+                }
+                // Not else! Note that mIsBeingDragged can be set above.
+                if (mIsBeingDragged) {
+                    // Scroll to follow the motion event
+                    final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+                    final float x = ev.getX(activePointerIndex);
+                    needsInvalidate |= performDrag(x);
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+                if (mIsBeingDragged) {
+                    final VelocityTracker velocityTracker = mVelocityTracker;
+                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+                    int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
+                    mPopulatePending = true;
+                    final int width = getClientWidth();
+                    final int scrollX = getScrollX();
+                    final ItemInfo ii = infoForCurrentScrollPosition();
+                    final int currentPage = ii.position;
+                    final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
+                    final int activePointerIndex =
+                            ev.findPointerIndex(mActivePointerId);
+                    final float x = ev.getX(activePointerIndex);
+                    final int totalDelta = (int) (x - mInitialMotionX);
+                    int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
+                            totalDelta);
+                    setCurrentItemInternal(nextPage, true, true, initialVelocity);
+
+                    mActivePointerId = INVALID_POINTER;
+                    endDrag();
+                    mLeftEdge.onRelease();
+                    mRightEdge.onRelease();
+                    needsInvalidate = true;
+                }
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                if (mIsBeingDragged) {
+                    scrollToItem(mCurItem, true, 0, false);
+                    mActivePointerId = INVALID_POINTER;
+                    endDrag();
+                    mLeftEdge.onRelease();
+                    mRightEdge.onRelease();
+                    needsInvalidate = true;
+                }
+                break;
+            case MotionEvent.ACTION_POINTER_DOWN: {
+                final int index = ev.getActionIndex();
+                final float x = ev.getX(index);
+                mLastMotionX = x;
+                mActivePointerId = ev.getPointerId(index);
+                break;
+            }
+            case MotionEvent.ACTION_POINTER_UP:
+                onSecondaryPointerUp(ev);
+                mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
+                break;
+        }
+        if (needsInvalidate) {
+            postInvalidateOnAnimation();
+        }
+        return true;
+    }
+
+    private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {
+        final ViewParent parent = getParent();
+        if (parent != null) {
+            parent.requestDisallowInterceptTouchEvent(disallowIntercept);
+        }
+    }
+
+    private boolean performDrag(float x) {
+        boolean needsInvalidate = false;
+
+        final float deltaX = mLastMotionX - x;
+        mLastMotionX = x;
+
+        float oldScrollX = getScrollX();
+        float scrollX = oldScrollX + deltaX;
+        final int width = getClientWidth();
+
+        float leftBound = width * mFirstOffset;
+        float rightBound = width * mLastOffset;
+        boolean leftAbsolute = true;
+        boolean rightAbsolute = true;
+
+        final ItemInfo firstItem = mItems.get(0);
+        final ItemInfo lastItem = mItems.get(mItems.size() - 1);
+        if (firstItem.position != 0) {
+            leftAbsolute = false;
+            leftBound = firstItem.offset * width;
+        }
+        if (lastItem.position != mAdapter.getCount() - 1) {
+            rightAbsolute = false;
+            rightBound = lastItem.offset * width;
+        }
+
+        if (scrollX < leftBound) {
+            if (leftAbsolute) {
+                float over = leftBound - scrollX;
+                mLeftEdge.onPull(Math.abs(over) / width);
+                needsInvalidate = true;
+            }
+            scrollX = leftBound;
+        } else if (scrollX > rightBound) {
+            if (rightAbsolute) {
+                float over = scrollX - rightBound;
+                mRightEdge.onPull(Math.abs(over) / width);
+                needsInvalidate = true;
+            }
+            scrollX = rightBound;
+        }
+        // Don't lose the rounded component
+        mLastMotionX += scrollX - (int) scrollX;
+        scrollTo((int) scrollX, getScrollY());
+        pageScrolled((int) scrollX);
+
+        return needsInvalidate;
+    }
+
+    /**
+     * @return Info about the page at the current scroll position.
+     *         This can be synthetic for a missing middle page; the 'object' field can be null.
+     */
+    private ItemInfo infoForCurrentScrollPosition() {
+        final int width = getClientWidth();
+        final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0;
+        final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
+        int lastPos = -1;
+        float lastOffset = 0.f;
+        float lastWidth = 0.f;
+        boolean first = true;
+
+        ItemInfo lastItem = null;
+        for (int i = 0; i < mItems.size(); i++) {
+            ItemInfo ii = mItems.get(i);
+            float offset;
+            if (!first && ii.position != lastPos + 1) {
+                // Create a synthetic item for a missing page.
+                ii = mTempItem;
+                ii.offset = lastOffset + lastWidth + marginOffset;
+                ii.position = lastPos + 1;
+                ii.widthFactor = mAdapter.getPageWidth(ii.position);
+                i--;
+            }
+            offset = ii.offset;
+
+            final float leftBound = offset;
+            final float rightBound = offset + ii.widthFactor + marginOffset;
+            if (first || scrollOffset >= leftBound) {
+                if (scrollOffset < rightBound || i == mItems.size() - 1) {
+                    return ii;
+                }
+            } else {
+                return lastItem;
+            }
+            first = false;
+            lastPos = ii.position;
+            lastOffset = offset;
+            lastWidth = ii.widthFactor;
+            lastItem = ii;
+        }
+
+        return lastItem;
+    }
+
+    private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) {
+        int targetPage;
+        if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
+            targetPage = velocity > 0 ? currentPage : currentPage + 1;
+        } else {
+            final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
+            targetPage = (int) (currentPage + pageOffset + truncator);
+        }
+
+        if (mItems.size() > 0) {
+            final ItemInfo firstItem = mItems.get(0);
+            final ItemInfo lastItem = mItems.get(mItems.size() - 1);
+
+            // Only let the user target pages we have items for
+            targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
+        }
+
+        return targetPage;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+        boolean needsInvalidate = false;
+
+        final int overScrollMode = getOverScrollMode();
+        if (overScrollMode == View.OVER_SCROLL_ALWAYS ||
+                (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS &&
+                        mAdapter != null && mAdapter.getCount() > 1)) {
+            if (!mLeftEdge.isFinished()) {
+                final int restoreCount = canvas.save();
+                final int height = getHeight() - getPaddingTop() - getPaddingBottom();
+                final int width = getWidth();
+
+                canvas.rotate(270);
+                canvas.translate(-height + getPaddingTop(), mFirstOffset * width);
+                mLeftEdge.setSize(height, width);
+                needsInvalidate |= mLeftEdge.draw(canvas);
+                canvas.restoreToCount(restoreCount);
+            }
+            if (!mRightEdge.isFinished()) {
+                final int restoreCount = canvas.save();
+                final int width = getWidth();
+                final int height = getHeight() - getPaddingTop() - getPaddingBottom();
+
+                canvas.rotate(90);
+                canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width);
+                mRightEdge.setSize(height, width);
+                needsInvalidate |= mRightEdge.draw(canvas);
+                canvas.restoreToCount(restoreCount);
+            }
+        } else {
+            mLeftEdge.finish();
+            mRightEdge.finish();
+        }
+
+        if (needsInvalidate) {
+            // Keep animating
+            postInvalidateOnAnimation();
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        // Draw the margin drawable between pages if needed.
+        if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) {
+            final int scrollX = getScrollX();
+            final int width = getWidth();
+
+            final float marginOffset = (float) mPageMargin / width;
+            int itemIndex = 0;
+            ItemInfo ii = mItems.get(0);
+            float offset = ii.offset;
+            final int itemCount = mItems.size();
+            final int firstPos = ii.position;
+            final int lastPos = mItems.get(itemCount - 1).position;
+            for (int pos = firstPos; pos < lastPos; pos++) {
+                while (pos > ii.position && itemIndex < itemCount) {
+                    ii = mItems.get(++itemIndex);
+                }
+
+                float drawAt;
+                if (pos == ii.position) {
+                    drawAt = (ii.offset + ii.widthFactor) * width;
+                    offset = ii.offset + ii.widthFactor + marginOffset;
+                } else {
+                    float widthFactor = mAdapter.getPageWidth(pos);
+                    drawAt = (offset + widthFactor) * width;
+                    offset += widthFactor + marginOffset;
+                }
+
+                if (drawAt + mPageMargin > scrollX) {
+                    mMarginDrawable.setBounds((int) drawAt, mTopPageBounds,
+                            (int) (drawAt + mPageMargin + 0.5f), mBottomPageBounds);
+                    mMarginDrawable.draw(canvas);
+                }
+
+                if (drawAt > scrollX + width) {
+                    break; // No more visible, no sense in continuing
+                }
+            }
+        }
+    }
+
+    /**
+     * Start a fake drag of the pager.
+     *
+     * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
+     * with the touch scrolling of another view, while still letting the ViewPager
+     * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
+     * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
+     * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
+     *
+     * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
+     * is already in progress, this method will return false.
+     *
+     * @return true if the fake drag began successfully, false if it could not be started.
+     *
+     * @see #fakeDragBy(float)
+     * @see #endFakeDrag()
+     */
+    public boolean beginFakeDrag() {
+        if (mIsBeingDragged) {
+            return false;
+        }
+        mFakeDragging = true;
+        setScrollState(SCROLL_STATE_DRAGGING);
+        mInitialMotionX = mLastMotionX = 0;
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        } else {
+            mVelocityTracker.clear();
+        }
+        final long time = SystemClock.uptimeMillis();
+        final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
+        mVelocityTracker.addMovement(ev);
+        ev.recycle();
+        mFakeDragBeginTime = time;
+        return true;
+    }
+
+    /**
+     * End a fake drag of the pager.
+     *
+     * @see #beginFakeDrag()
+     * @see #fakeDragBy(float)
+     */
+    public void endFakeDrag() {
+        if (!mFakeDragging) {
+            throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
+        }
+
+        final VelocityTracker velocityTracker = mVelocityTracker;
+        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+        int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
+        mPopulatePending = true;
+        final int width = getClientWidth();
+        final int scrollX = getScrollX();
+        final ItemInfo ii = infoForCurrentScrollPosition();
+        final int currentPage = ii.position;
+        final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
+        final int totalDelta = (int) (mLastMotionX - mInitialMotionX);
+        int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
+                totalDelta);
+        setCurrentItemInternal(nextPage, true, true, initialVelocity);
+        endDrag();
+
+        mFakeDragging = false;
+    }
+
+    /**
+     * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
+     *
+     * @param xOffset Offset in pixels to drag by.
+     * @see #beginFakeDrag()
+     * @see #endFakeDrag()
+     */
+    public void fakeDragBy(float xOffset) {
+        if (!mFakeDragging) {
+            throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
+        }
+
+        mLastMotionX += xOffset;
+
+        float oldScrollX = getScrollX();
+        float scrollX = oldScrollX - xOffset;
+        final int width = getClientWidth();
+
+        float leftBound = width * mFirstOffset;
+        float rightBound = width * mLastOffset;
+
+        final ItemInfo firstItem = mItems.get(0);
+        final ItemInfo lastItem = mItems.get(mItems.size() - 1);
+        if (firstItem.position != 0) {
+            leftBound = firstItem.offset * width;
+        }
+        if (lastItem.position != mAdapter.getCount() - 1) {
+            rightBound = lastItem.offset * width;
+        }
+
+        if (scrollX < leftBound) {
+            scrollX = leftBound;
+        } else if (scrollX > rightBound) {
+            scrollX = rightBound;
+        }
+        // Don't lose the rounded component
+        mLastMotionX += scrollX - (int) scrollX;
+        scrollTo((int) scrollX, getScrollY());
+        pageScrolled((int) scrollX);
+
+        // Synthesize an event for the VelocityTracker.
+        final long time = SystemClock.uptimeMillis();
+        final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
+                mLastMotionX, 0, 0);
+        mVelocityTracker.addMovement(ev);
+        ev.recycle();
+    }
+
+    /**
+     * Returns true if a fake drag is in progress.
+     *
+     * @return true if currently in a fake drag, false otherwise.
+     *
+     * @see #beginFakeDrag()
+     * @see #fakeDragBy(float)
+     * @see #endFakeDrag()
+     */
+    public boolean isFakeDragging() {
+        return mFakeDragging;
+    }
+
+    private void onSecondaryPointerUp(MotionEvent ev) {
+        final int pointerIndex = ev.getActionIndex();
+        final int pointerId = ev.getPointerId(pointerIndex);
+        if (pointerId == mActivePointerId) {
+            // This was our active pointer going up. Choose a new
+            // active pointer and adjust accordingly.
+            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+            mLastMotionX = ev.getX(newPointerIndex);
+            mActivePointerId = ev.getPointerId(newPointerIndex);
+            if (mVelocityTracker != null) {
+                mVelocityTracker.clear();
+            }
+        }
+    }
+
+    private void endDrag() {
+        mIsBeingDragged = false;
+        mIsUnableToDrag = false;
+
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+    }
+
+    private void setScrollingCacheEnabled(boolean enabled) {
+        if (mScrollingCacheEnabled != enabled) {
+            mScrollingCacheEnabled = enabled;
+            if (USE_CACHE) {
+                final int size = getChildCount();
+                for (int i = 0; i < size; ++i) {
+                    final View child = getChildAt(i);
+                    if (child.getVisibility() != GONE) {
+                        child.setDrawingCacheEnabled(enabled);
+                    }
+                }
+            }
+        }
+    }
+
+    public boolean canScrollHorizontally(int direction) {
+        if (mAdapter == null) {
+            return false;
+        }
+
+        final int width = getClientWidth();
+        final int scrollX = getScrollX();
+        if (direction < 0) {
+            return (scrollX > (int) (width * mFirstOffset));
+        } else if (direction > 0) {
+            return (scrollX < (int) (width * mLastOffset));
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Tests scrollability within child views of v given a delta of dx.
+     *
+     * @param v View to test for horizontal scrollability
+     * @param checkV Whether the view v passed should itself be checked for scrollability (true),
+     *               or just its children (false).
+     * @param dx Delta scrolled in pixels
+     * @param x X coordinate of the active touch point
+     * @param y Y coordinate of the active touch point
+     * @return true if child views of v can be scrolled by delta of dx.
+     */
+    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
+        if (v instanceof ViewGroup) {
+            final ViewGroup group = (ViewGroup) v;
+            final int scrollX = v.getScrollX();
+            final int scrollY = v.getScrollY();
+            final int count = group.getChildCount();
+            // Count backwards - let topmost views consume scroll distance first.
+            for (int i = count - 1; i >= 0; i--) {
+                // TODO: Add versioned support here for transformed views.
+                // This will not work for transformed views in Honeycomb+
+                final View child = group.getChildAt(i);
+                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
+                        y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
+                        canScroll(child, true, dx, x + scrollX - child.getLeft(),
+                                y + scrollY - child.getTop())) {
+                    return true;
+                }
+            }
+        }
+
+        return checkV && v.canScrollHorizontally(-dx);
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        // Let the focused view and/or our descendants get the key first
+        return super.dispatchKeyEvent(event) || executeKeyEvent(event);
+    }
+
+    /**
+     * You can call this function yourself to have the scroll view perform
+     * scrolling from a key event, just as if the event had been dispatched to
+     * it by the view hierarchy.
+     *
+     * @param event The key event to execute.
+     * @return Return true if the event was handled, else false.
+     */
+    public boolean executeKeyEvent(KeyEvent event) {
+        boolean handled = false;
+        if (event.getAction() == KeyEvent.ACTION_DOWN) {
+            switch (event.getKeyCode()) {
+                case KeyEvent.KEYCODE_DPAD_LEFT:
+                    handled = arrowScroll(FOCUS_LEFT);
+                    break;
+                case KeyEvent.KEYCODE_DPAD_RIGHT:
+                    handled = arrowScroll(FOCUS_RIGHT);
+                    break;
+                case KeyEvent.KEYCODE_TAB:
+                    if (event.hasNoModifiers()) {
+                        handled = arrowScroll(FOCUS_FORWARD);
+                    } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
+                        handled = arrowScroll(FOCUS_BACKWARD);
+                    }
+                    break;
+            }
+        }
+        return handled;
+    }
+
+    public boolean arrowScroll(int direction) {
+        View currentFocused = findFocus();
+        if (currentFocused == this) {
+            currentFocused = null;
+        } else if (currentFocused != null) {
+            boolean isChild = false;
+            for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
+                    parent = parent.getParent()) {
+                if (parent == this) {
+                    isChild = true;
+                    break;
+                }
+            }
+            if (!isChild) {
+                // This would cause the focus search down below to fail in fun ways.
+                final StringBuilder sb = new StringBuilder();
+                sb.append(currentFocused.getClass().getSimpleName());
+                for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
+                        parent = parent.getParent()) {
+                    sb.append(" => ").append(parent.getClass().getSimpleName());
+                }
+                Log.e(TAG, "arrowScroll tried to find focus based on non-child " +
+                        "current focused view " + sb.toString());
+                currentFocused = null;
+            }
+        }
+
+        boolean handled = false;
+
+        View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
+                direction);
+        if (nextFocused != null && nextFocused != currentFocused) {
+            if (direction == View.FOCUS_LEFT) {
+                // If there is nothing to the left, or this is causing us to
+                // jump to the right, then what we really want to do is page left.
+                final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;
+                final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;
+                if (currentFocused != null && nextLeft >= currLeft) {
+                    handled = pageLeft();
+                } else {
+                    handled = nextFocused.requestFocus();
+                }
+            } else if (direction == View.FOCUS_RIGHT) {
+                // If there is nothing to the right, or this is causing us to
+                // jump to the left, then what we really want to do is page right.
+                final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;
+                final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;
+                if (currentFocused != null && nextLeft <= currLeft) {
+                    handled = pageRight();
+                } else {
+                    handled = nextFocused.requestFocus();
+                }
+            }
+        } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {
+            // Trying to move left and nothing there; try to page.
+            handled = pageLeft();
+        } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {
+            // Trying to move right and nothing there; try to page.
+            handled = pageRight();
+        }
+        if (handled) {
+            playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
+        }
+        return handled;
+    }
+
+    private Rect getChildRectInPagerCoordinates(Rect outRect, View child) {
+        if (outRect == null) {
+            outRect = new Rect();
+        }
+        if (child == null) {
+            outRect.set(0, 0, 0, 0);
+            return outRect;
+        }
+        outRect.left = child.getLeft();
+        outRect.right = child.getRight();
+        outRect.top = child.getTop();
+        outRect.bottom = child.getBottom();
+
+        ViewParent parent = child.getParent();
+        while (parent instanceof ViewGroup && parent != this) {
+            final ViewGroup group = (ViewGroup) parent;
+            outRect.left += group.getLeft();
+            outRect.right += group.getRight();
+            outRect.top += group.getTop();
+            outRect.bottom += group.getBottom();
+
+            parent = group.getParent();
+        }
+        return outRect;
+    }
+
+    boolean pageLeft() {
+        if (mCurItem > 0) {
+            setCurrentItem(mCurItem-1, true);
+            return true;
+        }
+        return false;
+    }
+
+    boolean pageRight() {
+        if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) {
+            setCurrentItem(mCurItem+1, true);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * We only want the current page that is being shown to be focusable.
+     */
+    @Override
+    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+        final int focusableCount = views.size();
+
+        final int descendantFocusability = getDescendantFocusability();
+
+        if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
+            for (int i = 0; i < getChildCount(); i++) {
+                final View child = getChildAt(i);
+                if (child.getVisibility() == VISIBLE) {
+                    ItemInfo ii = infoForChild(child);
+                    if (ii != null && ii.position == mCurItem) {
+                        child.addFocusables(views, direction, focusableMode);
+                    }
+                }
+            }
+        }
+
+        // we add ourselves (if focusable) in all cases except for when we are
+        // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable.  this is
+        // to avoid the focus search finding layouts when a more precise search
+        // among the focusable children would be more interesting.
+        if (
+            descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
+                // No focusable descendants
+                (focusableCount == views.size())) {
+            // Note that we can't call the superclass here, because it will
+            // add all views in.  So we need to do the same thing View does.
+            if (!isFocusable()) {
+                return;
+            }
+            if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
+                    isInTouchMode() && !isFocusableInTouchMode()) {
+                return;
+            }
+            if (views != null) {
+                views.add(this);
+            }
+        }
+    }
+
+    /**
+     * We only want the current page that is being shown to be touchable.
+     */
+    @Override
+    public void addTouchables(ArrayList<View> views) {
+        // Note that we don't call super.addTouchables(), which means that
+        // we don't call View.addTouchables().  This is okay because a ViewPager
+        // is itself not touchable.
+        for (int i = 0; i < getChildCount(); i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() == VISIBLE) {
+                ItemInfo ii = infoForChild(child);
+                if (ii != null && ii.position == mCurItem) {
+                    child.addTouchables(views);
+                }
+            }
+        }
+    }
+
+    /**
+     * We only want the current page that is being shown to be focusable.
+     */
+    @Override
+    protected boolean onRequestFocusInDescendants(int direction,
+            Rect previouslyFocusedRect) {
+        int index;
+        int increment;
+        int end;
+        int count = getChildCount();
+        if ((direction & FOCUS_FORWARD) != 0) {
+            index = 0;
+            increment = 1;
+            end = count;
+        } else {
+            index = count - 1;
+            increment = -1;
+            end = -1;
+        }
+        for (int i = index; i != end; i += increment) {
+            View child = getChildAt(i);
+            if (child.getVisibility() == VISIBLE) {
+                ItemInfo ii = infoForChild(child);
+                if (ii != null && ii.position == mCurItem) {
+                    if (child.requestFocus(direction, previouslyFocusedRect)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        // Dispatch scroll events from this ViewPager.
+        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+            return super.dispatchPopulateAccessibilityEvent(event);
+        }
+
+        // Dispatch all other accessibility events from the current page.
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() == VISIBLE) {
+                final ItemInfo ii = infoForChild(child);
+                if (ii != null && ii.position == mCurItem &&
+                        child.dispatchPopulateAccessibilityEvent(event)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams();
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return generateDefaultLayoutParams();
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof LayoutParams && super.checkLayoutParams(p);
+    }
+
+    @Override
+    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new LayoutParams(getContext(), attrs);
+    }
+
+    class MyAccessibilityDelegate extends AccessibilityDelegate {
+
+        @Override
+        public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
+            super.onInitializeAccessibilityEvent(host, event);
+            event.setClassName(ViewPager.class.getName());
+            final AccessibilityRecord record = AccessibilityRecord.obtain();
+            record.setScrollable(canScroll());
+            if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED
+                    && mAdapter != null) {
+                record.setItemCount(mAdapter.getCount());
+                record.setFromIndex(mCurItem);
+                record.setToIndex(mCurItem);
+            }
+        }
+
+        @Override
+        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+            super.onInitializeAccessibilityNodeInfo(host, info);
+            info.setClassName(ViewPager.class.getName());
+            info.setScrollable(canScroll());
+            if (canScrollHorizontally(1)) {
+                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+            }
+            if (canScrollHorizontally(-1)) {
+                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+            }
+        }
+
+        @Override
+        public boolean performAccessibilityAction(View host, int action, Bundle args) {
+            if (super.performAccessibilityAction(host, action, args)) {
+                return true;
+            }
+            switch (action) {
+                case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
+                    if (canScrollHorizontally(1)) {
+                        setCurrentItem(mCurItem + 1);
+                        return true;
+                    }
+                } return false;
+                case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
+                    if (canScrollHorizontally(-1)) {
+                        setCurrentItem(mCurItem - 1);
+                        return true;
+                    }
+                } return false;
+            }
+            return false;
+        }
+
+        private boolean canScroll() {
+            return (mAdapter != null) && (mAdapter.getCount() > 1);
+        }
+    }
+
+    private class PagerObserver extends DataSetObserver {
+        @Override
+        public void onChanged() {
+            dataSetChanged();
+        }
+        @Override
+        public void onInvalidated() {
+            dataSetChanged();
+        }
+    }
+
+    /**
+     * Layout parameters that should be supplied for views added to a
+     * ViewPager.
+     */
+    public static class LayoutParams extends ViewGroup.LayoutParams {
+        /**
+         * true if this view is a decoration on the pager itself and not
+         * a view supplied by the adapter.
+         */
+        public boolean isDecor;
+
+        /**
+         * Gravity setting for use on decor views only:
+         * Where to position the view page within the overall ViewPager
+         * container; constants are defined in {@link android.view.Gravity}.
+         */
+        public int gravity;
+
+        /**
+         * Width as a 0-1 multiplier of the measured pager width
+         */
+        float widthFactor = 0.f;
+
+        /**
+         * true if this view was added during layout and needs to be measured
+         * before being positioned.
+         */
+        boolean needsMeasure;
+
+        /**
+         * Adapter position this view is for if !isDecor
+         */
+        int position;
+
+        /**
+         * Current child index within the ViewPager that this view occupies
+         */
+        int childIndex;
+
+        public LayoutParams() {
+            super(FILL_PARENT, FILL_PARENT);
+        }
+
+        public LayoutParams(Context context, AttributeSet attrs) {
+            super(context, attrs);
+
+            final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
+            gravity = a.getInteger(0, Gravity.TOP);
+            a.recycle();
+        }
+    }
+
+    static class ViewPositionComparator implements Comparator<View> {
+        @Override
+        public int compare(View lhs, View rhs) {
+            final LayoutParams llp = (LayoutParams) lhs.getLayoutParams();
+            final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams();
+            if (llp.isDecor != rlp.isDecor) {
+                return llp.isDecor ? 1 : -1;
+            }
+            return llp.position - rlp.position;
+        }
+    }
+}
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index bfa0534..39064ed 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -130,6 +130,100 @@
     return surfaceObj;
 }
 
+int android_view_Surface_mapPublicFormatToHalFormat(PublicFormat f) {
+
+    switch(f) {
+        case PublicFormat::JPEG:
+        case PublicFormat::DEPTH_POINT_CLOUD:
+            return HAL_PIXEL_FORMAT_BLOB;
+        case PublicFormat::DEPTH16:
+            return HAL_PIXEL_FORMAT_Y16;
+        case PublicFormat::RAW_SENSOR:
+            return HAL_PIXEL_FORMAT_RAW16;
+        default:
+            // Most formats map 1:1
+            return static_cast<int>(f);
+    }
+}
+
+android_dataspace android_view_Surface_mapPublicFormatToHalDataspace(
+        PublicFormat f) {
+    switch(f) {
+        case PublicFormat::JPEG:
+            return HAL_DATASPACE_JFIF;
+        case PublicFormat::DEPTH_POINT_CLOUD:
+        case PublicFormat::DEPTH16:
+            return HAL_DATASPACE_DEPTH;
+        case PublicFormat::RAW_SENSOR:
+        case PublicFormat::RAW10:
+            return HAL_DATASPACE_ARBITRARY;
+        case PublicFormat::YUV_420_888:
+        case PublicFormat::NV21:
+        case PublicFormat::YV12:
+            return HAL_DATASPACE_JFIF;
+        default:
+            // Most formats map to UNKNOWN
+            return HAL_DATASPACE_UNKNOWN;
+    }
+}
+
+PublicFormat android_view_Surface_mapHalFormatDataspaceToPublicFormat(
+        int format, android_dataspace dataSpace) {
+    switch(format) {
+        case HAL_PIXEL_FORMAT_RGBA_8888:
+        case HAL_PIXEL_FORMAT_RGBX_8888:
+        case HAL_PIXEL_FORMAT_RGB_888:
+        case HAL_PIXEL_FORMAT_RGB_565:
+        case HAL_PIXEL_FORMAT_Y8:
+        case HAL_PIXEL_FORMAT_RAW10:
+        case HAL_PIXEL_FORMAT_YCbCr_420_888:
+        case HAL_PIXEL_FORMAT_YV12:
+            // Enums overlap in both name and value
+            return static_cast<PublicFormat>(format);
+        case HAL_PIXEL_FORMAT_RAW16:
+            // Name differs, though value is the same
+            return PublicFormat::RAW_SENSOR;
+        case HAL_PIXEL_FORMAT_YCbCr_422_SP:
+            // Name differs, though the value is the same
+            return PublicFormat::NV16;
+        case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+            // Name differs, though the value is the same
+            return PublicFormat::NV21;
+        case HAL_PIXEL_FORMAT_YCbCr_422_I:
+            // Name differs, though the value is the same
+            return PublicFormat::YUY2;
+        case HAL_PIXEL_FORMAT_Y16:
+            // Dataspace-dependent
+            switch (dataSpace) {
+                case HAL_DATASPACE_DEPTH:
+                    return PublicFormat::DEPTH16;
+                default:
+                    // Assume non-depth Y16 is just Y16.
+                    return PublicFormat::Y16;
+            }
+            break;
+        case HAL_PIXEL_FORMAT_BLOB:
+            // Dataspace-dependent
+            switch (dataSpace) {
+                case HAL_DATASPACE_DEPTH:
+                    return PublicFormat::DEPTH_POINT_CLOUD;
+                case HAL_DATASPACE_JFIF:
+                    return PublicFormat::JPEG;
+                default:
+                    // Assume otherwise-marked blobs are also JPEG
+                    return PublicFormat::JPEG;
+            }
+            break;
+        case HAL_PIXEL_FORMAT_BGRA_8888:
+        case HAL_PIXEL_FORMAT_RAW_OPAQUE:
+        case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
+            // Not defined in public API
+            return PublicFormat::UNKNOWN;
+
+        default:
+            return PublicFormat::UNKNOWN;
+    }
+}
 // ----------------------------------------------------------------------------
 
 static inline bool isSurfaceValid(const sp<Surface>& sur) {
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 3efb9c0..49c4247 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -356,6 +356,38 @@
     public static final int RAW10 = 0x25;
 
     /**
+     * Android dense depth image format.
+     *
+     * Each pixel is 16 bits, representing a depth ranging measurement from
+     * a depth camera or similar sensor.
+     *
+     * <p>This format assumes
+     * <ul>
+     * <li>an even width</li>
+     * <li>an even height</li>
+     * <li>a horizontal stride multiple of 16 pixels</li>
+     * </ul>
+     * </p>
+     *
+     * <pre> y_size = stride * height </pre>
+     *
+     * When produced by a camera, the units are millimeters.
+     */
+    public static final int DEPTH16 = 0x44363159;
+
+    /**
+     * Android sparse depth point cloud format.
+     *
+     * <p>A variable-length list of 3D points, with each point represented
+     * by a triple of floats.</p>
+     *
+     * <p>The number of points is {@code (size of the buffer in bytes) / 12}.
+     *
+     * The coordinate system and units depend on the source of the point cloud data.
+     */
+    public static final int DEPTH_POINT_CLOUD = 0x101;
+
+    /**
      * Use this function to retrieve the number of bits per pixel of an
      * ImageFormat.
      *
@@ -376,6 +408,7 @@
             case Y8:
                 return 8;
             case Y16:
+            case DEPTH16:
                 return 16;
             case NV21:
                 return 12;
@@ -412,6 +445,8 @@
             case YUV_420_888:
             case RAW_SENSOR:
             case RAW10:
+            case DEPTH16:
+            case DEPTH_POINT_CLOUD:
                 return true;
         }
 
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
deleted file mode 100644
index 138d73a..0000000
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ /dev/null
@@ -1,578 +0,0 @@
-/*
- * 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.graphics.drawable;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.graphics.Canvas;
-import android.graphics.CanvasProperty;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.util.MathUtils;
-import android.view.HardwareCanvas;
-import android.view.RenderNodeAnimator;
-import android.view.animation.LinearInterpolator;
-
-import java.util.ArrayList;
-
-/**
- * Draws a Material ripple.
- */
-class Ripple {
-    private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
-    private static final TimeInterpolator DECEL_INTERPOLATOR = new LogInterpolator();
-
-    private static final float GLOBAL_SPEED = 1.0f;
-    private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024.0f * GLOBAL_SPEED;
-    private static final float WAVE_TOUCH_UP_ACCELERATION = 3400.0f * GLOBAL_SPEED;
-    private static final float WAVE_OPACITY_DECAY_VELOCITY = 3.0f / GLOBAL_SPEED;
-
-    private static final long RIPPLE_ENTER_DELAY = 80;
-
-    // Hardware animators.
-    private final ArrayList<RenderNodeAnimator> mRunningAnimations = new ArrayList<>();
-
-    private final RippleDrawable mOwner;
-
-    /** Bounds used for computing max radius. */
-    private final Rect mBounds;
-
-    /** Maximum ripple radius. */
-    private float mOuterRadius;
-
-    /** Screen density used to adjust pixel-based velocities. */
-    private float mDensity;
-
-    private float mStartingX;
-    private float mStartingY;
-    private float mClampedStartingX;
-    private float mClampedStartingY;
-
-    // Hardware rendering properties.
-    private CanvasProperty<Paint> mPropPaint;
-    private CanvasProperty<Float> mPropRadius;
-    private CanvasProperty<Float> mPropX;
-    private CanvasProperty<Float> mPropY;
-
-    // Software animators.
-    private ObjectAnimator mAnimRadius;
-    private ObjectAnimator mAnimOpacity;
-    private ObjectAnimator mAnimX;
-    private ObjectAnimator mAnimY;
-
-    // Temporary paint used for creating canvas properties.
-    private Paint mTempPaint;
-
-    // Software rendering properties.
-    private float mOpacity = 1;
-    private float mOuterX;
-    private float mOuterY;
-
-    // Values used to tween between the start and end positions.
-    private float mTweenRadius = 0;
-    private float mTweenX = 0;
-    private float mTweenY = 0;
-
-    /** Whether we should be drawing hardware animations. */
-    private boolean mHardwareAnimating;
-
-    /** Whether we can use hardware acceleration for the exit animation. */
-    private boolean mCanUseHardware;
-
-    /** Whether we have an explicit maximum radius. */
-    private boolean mHasMaxRadius;
-
-    /** Whether we were canceled externally and should avoid self-removal. */
-    private boolean mCanceled;
-
-    private boolean mHasPendingHardwareExit;
-    private int mPendingRadiusDuration;
-    private int mPendingOpacityDuration;
-
-    /**
-     * Creates a new ripple.
-     */
-    public Ripple(RippleDrawable owner, Rect bounds, float startingX, float startingY) {
-        mOwner = owner;
-        mBounds = bounds;
-
-        mStartingX = startingX;
-        mStartingY = startingY;
-    }
-
-    public void setup(float maxRadius, float density) {
-        if (maxRadius >= 0) {
-            mHasMaxRadius = true;
-            mOuterRadius = maxRadius;
-        } else {
-            final float halfWidth = mBounds.width() / 2.0f;
-            final float halfHeight = mBounds.height() / 2.0f;
-            mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
-        }
-
-        mOuterX = 0;
-        mOuterY = 0;
-        mDensity = density;
-
-        clampStartingPosition();
-    }
-
-    public boolean isHardwareAnimating() {
-        return mHardwareAnimating;
-    }
-
-    private void clampStartingPosition() {
-        final float cX = mBounds.exactCenterX();
-        final float cY = mBounds.exactCenterY();
-        final float dX = mStartingX - cX;
-        final float dY = mStartingY - cY;
-        final float r = mOuterRadius;
-        if (dX * dX + dY * dY > r * r) {
-            // Point is outside the circle, clamp to the circumference.
-            final double angle = Math.atan2(dY, dX);
-            mClampedStartingX = cX + (float) (Math.cos(angle) * r);
-            mClampedStartingY = cY + (float) (Math.sin(angle) * r);
-        } else {
-            mClampedStartingX = mStartingX;
-            mClampedStartingY = mStartingY;
-        }
-    }
-
-    public void onHotspotBoundsChanged() {
-        if (!mHasMaxRadius) {
-            final float halfWidth = mBounds.width() / 2.0f;
-            final float halfHeight = mBounds.height() / 2.0f;
-            mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
-
-            clampStartingPosition();
-        }
-    }
-
-    public void setOpacity(float a) {
-        mOpacity = a;
-        invalidateSelf();
-    }
-
-    public float getOpacity() {
-        return mOpacity;
-    }
-
-    @SuppressWarnings("unused")
-    public void setRadiusGravity(float r) {
-        mTweenRadius = r;
-        invalidateSelf();
-    }
-
-    @SuppressWarnings("unused")
-    public float getRadiusGravity() {
-        return mTweenRadius;
-    }
-
-    @SuppressWarnings("unused")
-    public void setXGravity(float x) {
-        mTweenX = x;
-        invalidateSelf();
-    }
-
-    @SuppressWarnings("unused")
-    public float getXGravity() {
-        return mTweenX;
-    }
-
-    @SuppressWarnings("unused")
-    public void setYGravity(float y) {
-        mTweenY = y;
-        invalidateSelf();
-    }
-
-    @SuppressWarnings("unused")
-    public float getYGravity() {
-        return mTweenY;
-    }
-
-    /**
-     * Draws the ripple centered at (0,0) using the specified paint.
-     */
-    public boolean draw(Canvas c, Paint p) {
-        final boolean canUseHardware = c.isHardwareAccelerated();
-        if (mCanUseHardware != canUseHardware && mCanUseHardware) {
-            // We've switched from hardware to non-hardware mode. Panic.
-            cancelHardwareAnimations(true);
-        }
-        mCanUseHardware = canUseHardware;
-
-        final boolean hasContent;
-        if (canUseHardware && (mHardwareAnimating || mHasPendingHardwareExit)) {
-            hasContent = drawHardware((HardwareCanvas) c, p);
-        } else {
-            hasContent = drawSoftware(c, p);
-        }
-
-        return hasContent;
-    }
-
-    private boolean drawHardware(HardwareCanvas c, Paint p) {
-        if (mHasPendingHardwareExit) {
-            cancelHardwareAnimations(false);
-            startPendingHardwareExit(c, p);
-        }
-
-        c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
-
-        return true;
-    }
-
-    private boolean drawSoftware(Canvas c, Paint p) {
-        boolean hasContent = false;
-
-        final int paintAlpha = p.getAlpha();
-        final int alpha = (int) (paintAlpha * mOpacity + 0.5f);
-        final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
-        if (alpha > 0 && radius > 0) {
-            final float x = MathUtils.lerp(
-                    mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
-            final float y = MathUtils.lerp(
-                    mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
-            p.setAlpha(alpha);
-            c.drawCircle(x, y, radius, p);
-            p.setAlpha(paintAlpha);
-            hasContent = true;
-        }
-
-        return hasContent;
-    }
-
-    /**
-     * Returns the maximum bounds of the ripple relative to the ripple center.
-     */
-    public void getBounds(Rect bounds) {
-        final int outerX = (int) mOuterX;
-        final int outerY = (int) mOuterY;
-        final int r = (int) mOuterRadius + 1;
-        bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
-    }
-
-    /**
-     * Specifies the starting position relative to the drawable bounds. No-op if
-     * the ripple has already entered.
-     */
-    public void move(float x, float y) {
-        mStartingX = x;
-        mStartingY = y;
-
-        clampStartingPosition();
-    }
-
-    /**
-     * Starts the enter animation.
-     */
-    public void enter() {
-        cancel();
-
-        final int radiusDuration = (int)
-                (1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);
-
-        final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radiusGravity", 1);
-        radius.setAutoCancel(true);
-        radius.setDuration(radiusDuration);
-        radius.setInterpolator(LINEAR_INTERPOLATOR);
-        radius.setStartDelay(RIPPLE_ENTER_DELAY);
-
-        final ObjectAnimator cX = ObjectAnimator.ofFloat(this, "xGravity", 1);
-        cX.setAutoCancel(true);
-        cX.setDuration(radiusDuration);
-        cX.setInterpolator(LINEAR_INTERPOLATOR);
-        cX.setStartDelay(RIPPLE_ENTER_DELAY);
-
-        final ObjectAnimator cY = ObjectAnimator.ofFloat(this, "yGravity", 1);
-        cY.setAutoCancel(true);
-        cY.setDuration(radiusDuration);
-        cY.setInterpolator(LINEAR_INTERPOLATOR);
-        cY.setStartDelay(RIPPLE_ENTER_DELAY);
-
-        mAnimRadius = radius;
-        mAnimX = cX;
-        mAnimY = cY;
-
-        // Enter animations always run on the UI thread, since it's unlikely
-        // that anything interesting is happening until the user lifts their
-        // finger.
-        radius.start();
-        cX.start();
-        cY.start();
-    }
-
-    /**
-     * Starts the exit animation.
-     */
-    public void exit() {
-        final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
-        final float remaining;
-        if (mAnimRadius != null && mAnimRadius.isRunning()) {
-            remaining = mOuterRadius - radius;
-        } else {
-            remaining = mOuterRadius;
-        }
-
-        cancel();
-
-        final int radiusDuration = (int) (1000 * Math.sqrt(remaining / (WAVE_TOUCH_UP_ACCELERATION
-                + WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5);
-        final int opacityDuration = (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
-
-        if (mCanUseHardware) {
-            createPendingHardwareExit(radiusDuration, opacityDuration);
-        } else {
-            exitSoftware(radiusDuration, opacityDuration);
-        }
-    }
-
-    private void createPendingHardwareExit(int radiusDuration, int opacityDuration) {
-        mHasPendingHardwareExit = true;
-        mPendingRadiusDuration = radiusDuration;
-        mPendingOpacityDuration = opacityDuration;
-
-        // The animation will start on the next draw().
-        invalidateSelf();
-    }
-
-    private void startPendingHardwareExit(HardwareCanvas c, Paint p) {
-        mHasPendingHardwareExit = false;
-
-        final int radiusDuration = mPendingRadiusDuration;
-        final int opacityDuration = mPendingOpacityDuration;
-
-        final float startX = MathUtils.lerp(
-                mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
-        final float startY = MathUtils.lerp(
-                mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
-
-        final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
-        final Paint paint = getTempPaint(p);
-        paint.setAlpha((int) (paint.getAlpha() * mOpacity + 0.5f));
-        mPropPaint = CanvasProperty.createPaint(paint);
-        mPropRadius = CanvasProperty.createFloat(startRadius);
-        mPropX = CanvasProperty.createFloat(startX);
-        mPropY = CanvasProperty.createFloat(startY);
-
-        final RenderNodeAnimator radiusAnim = new RenderNodeAnimator(mPropRadius, mOuterRadius);
-        radiusAnim.setDuration(radiusDuration);
-        radiusAnim.setInterpolator(DECEL_INTERPOLATOR);
-        radiusAnim.setTarget(c);
-        radiusAnim.start();
-
-        final RenderNodeAnimator xAnim = new RenderNodeAnimator(mPropX, mOuterX);
-        xAnim.setDuration(radiusDuration);
-        xAnim.setInterpolator(DECEL_INTERPOLATOR);
-        xAnim.setTarget(c);
-        xAnim.start();
-
-        final RenderNodeAnimator yAnim = new RenderNodeAnimator(mPropY, mOuterY);
-        yAnim.setDuration(radiusDuration);
-        yAnim.setInterpolator(DECEL_INTERPOLATOR);
-        yAnim.setTarget(c);
-        yAnim.start();
-
-        final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPropPaint,
-                RenderNodeAnimator.PAINT_ALPHA, 0);
-        opacityAnim.setDuration(opacityDuration);
-        opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
-        opacityAnim.addListener(mAnimationListener);
-        opacityAnim.setTarget(c);
-        opacityAnim.start();
-
-        mRunningAnimations.add(radiusAnim);
-        mRunningAnimations.add(opacityAnim);
-        mRunningAnimations.add(xAnim);
-        mRunningAnimations.add(yAnim);
-
-        mHardwareAnimating = true;
-
-        // Set up the software values to match the hardware end values.
-        mOpacity = 0;
-        mTweenX = 1;
-        mTweenY = 1;
-        mTweenRadius = 1;
-    }
-
-    /**
-     * Jump all animations to their end state. The caller is responsible for
-     * removing the ripple from the list of animating ripples.
-     */
-    public void jump() {
-        mCanceled = true;
-        endSoftwareAnimations();
-        cancelHardwareAnimations(true);
-        mCanceled = false;
-    }
-
-    private void endSoftwareAnimations() {
-        if (mAnimRadius != null) {
-            mAnimRadius.end();
-            mAnimRadius = null;
-        }
-
-        if (mAnimOpacity != null) {
-            mAnimOpacity.end();
-            mAnimOpacity = null;
-        }
-
-        if (mAnimX != null) {
-            mAnimX.end();
-            mAnimX = null;
-        }
-
-        if (mAnimY != null) {
-            mAnimY.end();
-            mAnimY = null;
-        }
-    }
-
-    private Paint getTempPaint(Paint original) {
-        if (mTempPaint == null) {
-            mTempPaint = new Paint();
-        }
-        mTempPaint.set(original);
-        return mTempPaint;
-    }
-
-    private void exitSoftware(int radiusDuration, int opacityDuration) {
-        final ObjectAnimator radiusAnim = ObjectAnimator.ofFloat(this, "radiusGravity", 1);
-        radiusAnim.setAutoCancel(true);
-        radiusAnim.setDuration(radiusDuration);
-        radiusAnim.setInterpolator(DECEL_INTERPOLATOR);
-
-        final ObjectAnimator xAnim = ObjectAnimator.ofFloat(this, "xGravity", 1);
-        xAnim.setAutoCancel(true);
-        xAnim.setDuration(radiusDuration);
-        xAnim.setInterpolator(DECEL_INTERPOLATOR);
-
-        final ObjectAnimator yAnim = ObjectAnimator.ofFloat(this, "yGravity", 1);
-        yAnim.setAutoCancel(true);
-        yAnim.setDuration(radiusDuration);
-        yAnim.setInterpolator(DECEL_INTERPOLATOR);
-
-        final ObjectAnimator opacityAnim = ObjectAnimator.ofFloat(this, "opacity", 0);
-        opacityAnim.setAutoCancel(true);
-        opacityAnim.setDuration(opacityDuration);
-        opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
-        opacityAnim.addListener(mAnimationListener);
-
-        mAnimRadius = radiusAnim;
-        mAnimOpacity = opacityAnim;
-        mAnimX = xAnim;
-        mAnimY = yAnim;
-
-        radiusAnim.start();
-        opacityAnim.start();
-        xAnim.start();
-        yAnim.start();
-    }
-
-    /**
-     * Cancels all animations. The caller is responsible for removing
-     * the ripple from the list of animating ripples.
-     */
-    public void cancel() {
-        mCanceled = true;
-        cancelSoftwareAnimations();
-        cancelHardwareAnimations(false);
-        mCanceled = false;
-    }
-
-    private void cancelSoftwareAnimations() {
-        if (mAnimRadius != null) {
-            mAnimRadius.cancel();
-            mAnimRadius = null;
-        }
-
-        if (mAnimOpacity != null) {
-            mAnimOpacity.cancel();
-            mAnimOpacity = null;
-        }
-
-        if (mAnimX != null) {
-            mAnimX.cancel();
-            mAnimX = null;
-        }
-
-        if (mAnimY != null) {
-            mAnimY.cancel();
-            mAnimY = null;
-        }
-    }
-
-    /**
-     * Cancels any running hardware animations.
-     */
-    private void cancelHardwareAnimations(boolean jumpToEnd) {
-        final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
-        final int N = runningAnimations.size();
-        for (int i = 0; i < N; i++) {
-            if (jumpToEnd) {
-                runningAnimations.get(i).end();
-            } else {
-                runningAnimations.get(i).cancel();
-            }
-        }
-        runningAnimations.clear();
-
-        if (mHasPendingHardwareExit) {
-            // If we had a pending hardware exit, jump to the end state.
-            mHasPendingHardwareExit = false;
-
-            if (jumpToEnd) {
-                mOpacity = 0;
-                mTweenX = 1;
-                mTweenY = 1;
-                mTweenRadius = 1;
-            }
-        }
-
-        mHardwareAnimating = false;
-    }
-
-    private void removeSelf() {
-        // The owner will invalidate itself.
-        if (!mCanceled) {
-            mOwner.removeRipple(this);
-        }
-    }
-
-    private void invalidateSelf() {
-        mOwner.invalidateSelf();
-    }
-
-    private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() {
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            removeSelf();
-        }
-    };
-
-    /**
-    * Interpolator with a smooth log deceleration
-    */
-    private static final class LogInterpolator implements TimeInterpolator {
-        @Override
-        public float getInterpolation(float input) {
-            return 1 - (float) Math.pow(400, -input * 1.4);
-        }
-    }
-}
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index ef35289..6d1b1fe 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -17,432 +17,162 @@
 package android.graphics.drawable;
 
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.graphics.Canvas;
 import android.graphics.CanvasProperty;
-import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Rect;
-import android.util.MathUtils;
+import android.util.FloatProperty;
 import android.view.HardwareCanvas;
 import android.view.RenderNodeAnimator;
 import android.view.animation.LinearInterpolator;
 
-import java.util.ArrayList;
-
 /**
- * Draws a Material ripple.
+ * Draws a ripple background.
  */
-class RippleBackground {
+class RippleBackground extends RippleComponent {
     private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
 
-    private static final float GLOBAL_SPEED = 1.0f;
-    private static final float WAVE_OPACITY_DECAY_VELOCITY = 3.0f / GLOBAL_SPEED;
-    private static final float WAVE_OUTER_OPACITY_EXIT_VELOCITY_MAX = 4.5f * GLOBAL_SPEED;
-    private static final float WAVE_OUTER_OPACITY_EXIT_VELOCITY_MIN = 1.5f * GLOBAL_SPEED;
-    private static final float WAVE_OUTER_SIZE_INFLUENCE_MAX = 200f;
-    private static final float WAVE_OUTER_SIZE_INFLUENCE_MIN = 40f;
-
-    private static final int ENTER_DURATION = 667;
-    private static final int ENTER_DURATION_FAST = 100;
-
-    // Hardware animators.
-    private final ArrayList<RenderNodeAnimator> mRunningAnimations = new ArrayList<>();
-
-    private final RippleDrawable mOwner;
-
-    /** Bounds used for computing max radius. */
-    private final Rect mBounds;
-
-    /** ARGB color for drawing this ripple. */
-    private int mColor;
-
-    /** Maximum ripple radius. */
-    private float mOuterRadius;
-
-    /** Screen density used to adjust pixel-based velocities. */
-    private float mDensity;
+    private static final int OPACITY_ENTER_DURATION = 600;
+    private static final int OPACITY_ENTER_DURATION_FAST = 120;
+    private static final int OPACITY_EXIT_DURATION = 480;
 
     // Hardware rendering properties.
-    private CanvasProperty<Paint> mPropOuterPaint;
-    private CanvasProperty<Float> mPropOuterRadius;
-    private CanvasProperty<Float> mPropOuterX;
-    private CanvasProperty<Float> mPropOuterY;
-
-    // Software animators.
-    private ObjectAnimator mAnimOuterOpacity;
-
-    // Temporary paint used for creating canvas properties.
-    private Paint mTempPaint;
+    private CanvasProperty<Paint> mPropPaint;
+    private CanvasProperty<Float> mPropRadius;
+    private CanvasProperty<Float> mPropX;
+    private CanvasProperty<Float> mPropY;
 
     // Software rendering properties.
-    private float mOuterOpacity = 0;
-    private float mOuterX;
-    private float mOuterY;
+    private float mOpacity = 0;
 
-    /** Whether we should be drawing hardware animations. */
-    private boolean mHardwareAnimating;
-
-    /** Whether we can use hardware acceleration for the exit animation. */
-    private boolean mCanUseHardware;
-
-    /** Whether we have an explicit maximum radius. */
-    private boolean mHasMaxRadius;
-
-    private boolean mHasPendingHardwareExit;
-    private int mPendingOpacityDuration;
-    private int mPendingInflectionDuration;
-    private int mPendingInflectionOpacity;
-
-    /**
-     * Creates a new ripple.
-     */
     public RippleBackground(RippleDrawable owner, Rect bounds) {
-        mOwner = owner;
-        mBounds = bounds;
+        super(owner, bounds);
     }
 
-    public void setup(float maxRadius, float density) {
-        if (maxRadius >= 0) {
-            mHasMaxRadius = true;
-            mOuterRadius = maxRadius;
-        } else {
-            final float halfWidth = mBounds.width() / 2.0f;
-            final float halfHeight = mBounds.height() / 2.0f;
-            mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
-        }
-
-        mOuterX = 0;
-        mOuterY = 0;
-        mDensity = density;
+    public boolean isVisible() {
+        return mOpacity > 0 || isHardwareAnimating();
     }
 
-    public void onHotspotBoundsChanged() {
-        if (!mHasMaxRadius) {
-            final float halfWidth = mBounds.width() / 2.0f;
-            final float halfHeight = mBounds.height() / 2.0f;
-            mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
-        }
-    }
-
-    @SuppressWarnings("unused")
-    public void setOuterOpacity(float a) {
-        mOuterOpacity = a;
-        invalidateSelf();
-    }
-
-    @SuppressWarnings("unused")
-    public float getOuterOpacity() {
-        return mOuterOpacity;
-    }
-
-    /**
-     * Draws the ripple centered at (0,0) using the specified paint.
-     */
-    public boolean draw(Canvas c, Paint p) {
-        mColor = p.getColor();
-
-        final boolean canUseHardware = c.isHardwareAccelerated();
-        if (mCanUseHardware != canUseHardware && mCanUseHardware) {
-            // We've switched from hardware to non-hardware mode. Panic.
-            cancelHardwareAnimations(true);
-        }
-        mCanUseHardware = canUseHardware;
-
-        final boolean hasContent;
-        if (canUseHardware && (mHardwareAnimating || mHasPendingHardwareExit)) {
-            hasContent = drawHardware((HardwareCanvas) c, p);
-        } else {
-            hasContent = drawSoftware(c, p);
-        }
-
-        return hasContent;
-    }
-
-    public boolean shouldDraw() {
-        return (mCanUseHardware && mHardwareAnimating) || (mOuterOpacity > 0 && mOuterRadius > 0);
-    }
-
-    private boolean drawHardware(HardwareCanvas c, Paint p) {
-        if (mHasPendingHardwareExit) {
-            cancelHardwareAnimations(false);
-            startPendingHardwareExit(c, p);
-        }
-
-        c.drawCircle(mPropOuterX, mPropOuterY, mPropOuterRadius, mPropOuterPaint);
-
-        return true;
-    }
-
-    private boolean drawSoftware(Canvas c, Paint p) {
+    @Override
+    protected boolean drawSoftware(Canvas c, Paint p) {
         boolean hasContent = false;
 
-        final int paintAlpha = p.getAlpha();
-        final int alpha = (int) (paintAlpha * mOuterOpacity + 0.5f);
-        final float radius = mOuterRadius;
-        if (alpha > 0 && radius > 0) {
+        final int origAlpha = p.getAlpha();
+        final int alpha = (int) (origAlpha * mOpacity + 0.5f);
+        if (alpha > 0) {
             p.setAlpha(alpha);
-            c.drawCircle(mOuterX, mOuterY, radius, p);
-            p.setAlpha(paintAlpha);
+            c.drawCircle(0, 0, mTargetRadius, p);
+            p.setAlpha(origAlpha);
             hasContent = true;
         }
 
         return hasContent;
     }
 
-    /**
-     * Returns the maximum bounds of the ripple relative to the ripple center.
-     */
-    public void getBounds(Rect bounds) {
-        final int outerX = (int) mOuterX;
-        final int outerY = (int) mOuterY;
-        final int r = (int) mOuterRadius + 1;
-        bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
+    @Override
+    protected boolean drawHardware(HardwareCanvas c) {
+        c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
+        return true;
     }
 
-    /**
-     * Starts the enter animation.
-     */
-    public void enter(boolean fast) {
-        cancel();
+    @Override
+    protected Animator createSoftwareEnter(boolean fast) {
+        // Linear enter based on current opacity.
+        final int maxDuration = fast ? OPACITY_ENTER_DURATION_FAST : OPACITY_ENTER_DURATION;
+        final int duration = (int) ((1 - mOpacity) * maxDuration);
 
-        final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, "outerOpacity", 0, 1);
+        final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
         opacity.setAutoCancel(true);
-        opacity.setDuration(fast ? ENTER_DURATION_FAST : ENTER_DURATION);
+        opacity.setDuration(duration);
         opacity.setInterpolator(LINEAR_INTERPOLATOR);
 
-        mAnimOuterOpacity = opacity;
-
-        // Enter animations always run on the UI thread, since it's unlikely
-        // that anything interesting is happening until the user lifts their
-        // finger.
-        opacity.start();
+        return opacity;
     }
 
-    /**
-     * Starts the exit animation.
-     */
-    public void exit() {
-        cancel();
+    @Override
+    protected Animator createSoftwareExit() {
+        final AnimatorSet set = new AnimatorSet();
 
-        // Scale the outer max opacity and opacity velocity based
-        // on the size of the outer radius.
-        final int opacityDuration = (int) (1000 / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
-        final float outerSizeInfluence = MathUtils.constrain(
-                (mOuterRadius - WAVE_OUTER_SIZE_INFLUENCE_MIN * mDensity)
-                / (WAVE_OUTER_SIZE_INFLUENCE_MAX * mDensity), 0, 1);
-        final float outerOpacityVelocity = MathUtils.lerp(WAVE_OUTER_OPACITY_EXIT_VELOCITY_MIN,
-                WAVE_OUTER_OPACITY_EXIT_VELOCITY_MAX, outerSizeInfluence);
+        // Linear exit after enter is completed.
+        final ObjectAnimator exit = ObjectAnimator.ofFloat(this, RippleBackground.OPACITY, 0);
+        exit.setInterpolator(LINEAR_INTERPOLATOR);
+        exit.setDuration(OPACITY_EXIT_DURATION);
+        exit.setAutoCancel(true);
 
-        // Determine at what time the inner and outer opacity intersect.
-        // inner(t) = mOpacity - t * WAVE_OPACITY_DECAY_VELOCITY / 1000
-        // outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000
-        final int inflectionDuration = Math.max(0, (int) (1000 * (1 - mOuterOpacity)
-                / (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f));
-        final int inflectionOpacity = (int) (Color.alpha(mColor) * (mOuterOpacity
-                + inflectionDuration * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);
+        final AnimatorSet.Builder builder = set.play(exit);
 
-        if (mCanUseHardware) {
-            createPendingHardwareExit(opacityDuration, inflectionDuration, inflectionOpacity);
-        } else {
-            exitSoftware(opacityDuration, inflectionDuration, inflectionOpacity);
+        // Linear "fast" enter based on current opacity.
+        final int fastEnterDuration = (int) ((1 - mOpacity) * OPACITY_ENTER_DURATION_FAST);
+        if (fastEnterDuration > 0) {
+            final ObjectAnimator enter = ObjectAnimator.ofFloat(this, RippleBackground.OPACITY, 1);
+            enter.setInterpolator(LINEAR_INTERPOLATOR);
+            enter.setDuration(fastEnterDuration);
+            enter.setAutoCancel(true);
+
+            builder.after(enter);
+        }
+
+        return set;
+    }
+
+    @Override
+    protected RenderNodeAnimatorSet createHardwareExit(Paint p) {
+        final RenderNodeAnimatorSet set = new RenderNodeAnimatorSet();
+
+        final int targetAlpha = p.getAlpha();
+        final int currentAlpha = (int) (mOpacity * targetAlpha + 0.5f);
+        p.setAlpha(currentAlpha);
+
+        mPropPaint = CanvasProperty.createPaint(p);
+        mPropRadius = CanvasProperty.createFloat(mTargetRadius);
+        mPropX = CanvasProperty.createFloat(0);
+        mPropY = CanvasProperty.createFloat(0);
+
+        // Linear "fast" enter based on current opacity.
+        final int fastEnterDuration = (int) ((1 - mOpacity) * OPACITY_ENTER_DURATION_FAST);
+        if (fastEnterDuration > 0) {
+            final RenderNodeAnimator enter = new RenderNodeAnimator(
+                    mPropPaint, RenderNodeAnimator.PAINT_ALPHA, targetAlpha);
+            enter.setInterpolator(LINEAR_INTERPOLATOR);
+            enter.setDuration(fastEnterDuration);
+            set.add(enter);
+        }
+
+        // Linear exit after enter is completed.
+        final RenderNodeAnimator exit = new RenderNodeAnimator(
+                mPropPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
+        exit.setInterpolator(LINEAR_INTERPOLATOR);
+        exit.setDuration(OPACITY_EXIT_DURATION);
+        exit.setStartDelay(fastEnterDuration);
+        set.add(exit);
+
+        return set;
+    }
+
+    @Override
+    protected void jumpValuesToExit() {
+        mOpacity = 0;
+    }
+
+    private static abstract class BackgroundProperty extends FloatProperty<RippleBackground> {
+        public BackgroundProperty(String name) {
+            super(name);
         }
     }
 
-    private void createPendingHardwareExit(
-            int opacityDuration, int inflectionDuration, int inflectionOpacity) {
-        mHasPendingHardwareExit = true;
-        mPendingOpacityDuration = opacityDuration;
-        mPendingInflectionDuration = inflectionDuration;
-        mPendingInflectionOpacity = inflectionOpacity;
-
-        // The animation will start on the next draw().
-        invalidateSelf();
-    }
-
-    private void startPendingHardwareExit(HardwareCanvas c, Paint p) {
-        mHasPendingHardwareExit = false;
-
-        final int opacityDuration = mPendingOpacityDuration;
-        final int inflectionDuration = mPendingInflectionDuration;
-        final int inflectionOpacity = mPendingInflectionOpacity;
-
-        final Paint outerPaint = getTempPaint(p);
-        outerPaint.setAlpha((int) (outerPaint.getAlpha() * mOuterOpacity + 0.5f));
-        mPropOuterPaint = CanvasProperty.createPaint(outerPaint);
-        mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius);
-        mPropOuterX = CanvasProperty.createFloat(mOuterX);
-        mPropOuterY = CanvasProperty.createFloat(mOuterY);
-
-        final RenderNodeAnimator outerOpacityAnim;
-        if (inflectionDuration > 0) {
-            // Outer opacity continues to increase for a bit.
-            outerOpacityAnim = new RenderNodeAnimator(mPropOuterPaint,
-                    RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity);
-            outerOpacityAnim.setDuration(inflectionDuration);
-            outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
-
-            // Chain the outer opacity exit animation.
-            final int outerDuration = opacityDuration - inflectionDuration;
-            if (outerDuration > 0) {
-                final RenderNodeAnimator outerFadeOutAnim = new RenderNodeAnimator(
-                        mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
-                outerFadeOutAnim.setDuration(outerDuration);
-                outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
-                outerFadeOutAnim.setStartDelay(inflectionDuration);
-                outerFadeOutAnim.setStartValue(inflectionOpacity);
-                outerFadeOutAnim.addListener(mAnimationListener);
-                outerFadeOutAnim.setTarget(c);
-                outerFadeOutAnim.start();
-
-                mRunningAnimations.add(outerFadeOutAnim);
-            } else {
-                outerOpacityAnim.addListener(mAnimationListener);
-            }
-        } else {
-            outerOpacityAnim = new RenderNodeAnimator(
-                    mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
-            outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
-            outerOpacityAnim.setDuration(opacityDuration);
-            outerOpacityAnim.addListener(mAnimationListener);
-        }
-
-        outerOpacityAnim.setTarget(c);
-        outerOpacityAnim.start();
-
-        mRunningAnimations.add(outerOpacityAnim);
-
-        mHardwareAnimating = true;
-
-        // Set up the software values to match the hardware end values.
-        mOuterOpacity = 0;
-    }
-
-    /**
-     * Jump all animations to their end state. The caller is responsible for
-     * removing the ripple from the list of animating ripples.
-     */
-    public void jump() {
-        endSoftwareAnimations();
-        cancelHardwareAnimations(true);
-    }
-
-    private void endSoftwareAnimations() {
-        if (mAnimOuterOpacity != null) {
-            mAnimOuterOpacity.end();
-            mAnimOuterOpacity = null;
-        }
-    }
-
-    private Paint getTempPaint(Paint original) {
-        if (mTempPaint == null) {
-            mTempPaint = new Paint();
-        }
-        mTempPaint.set(original);
-        return mTempPaint;
-    }
-
-    private void exitSoftware(int opacityDuration, int inflectionDuration, int inflectionOpacity) {
-        final ObjectAnimator outerOpacityAnim;
-        if (inflectionDuration > 0) {
-            // Outer opacity continues to increase for a bit.
-            outerOpacityAnim = ObjectAnimator.ofFloat(this,
-                    "outerOpacity", inflectionOpacity / 255.0f);
-            outerOpacityAnim.setAutoCancel(true);
-            outerOpacityAnim.setDuration(inflectionDuration);
-            outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
-
-            // Chain the outer opacity exit animation.
-            final int outerDuration = opacityDuration - inflectionDuration;
-            if (outerDuration > 0) {
-                outerOpacityAnim.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        final ObjectAnimator outerFadeOutAnim = ObjectAnimator.ofFloat(
-                                RippleBackground.this, "outerOpacity", 0);
-                        outerFadeOutAnim.setAutoCancel(true);
-                        outerFadeOutAnim.setDuration(outerDuration);
-                        outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
-                        outerFadeOutAnim.addListener(mAnimationListener);
-
-                        mAnimOuterOpacity = outerFadeOutAnim;
-
-                        outerFadeOutAnim.start();
-                    }
-
-                    @Override
-                    public void onAnimationCancel(Animator animation) {
-                        animation.removeListener(this);
-                    }
-                });
-            } else {
-                outerOpacityAnim.addListener(mAnimationListener);
-            }
-        } else {
-            outerOpacityAnim = ObjectAnimator.ofFloat(this, "outerOpacity", 0);
-            outerOpacityAnim.setAutoCancel(true);
-            outerOpacityAnim.setDuration(opacityDuration);
-            outerOpacityAnim.addListener(mAnimationListener);
-        }
-
-        mAnimOuterOpacity = outerOpacityAnim;
-
-        outerOpacityAnim.start();
-    }
-
-    /**
-     * Cancel all animations. The caller is responsible for removing
-     * the ripple from the list of animating ripples.
-     */
-    public void cancel() {
-        cancelSoftwareAnimations();
-        cancelHardwareAnimations(false);
-    }
-
-    private void cancelSoftwareAnimations() {
-        if (mAnimOuterOpacity != null) {
-            mAnimOuterOpacity.cancel();
-            mAnimOuterOpacity = null;
-        }
-    }
-
-    /**
-     * Cancels any running hardware animations.
-     */
-    private void cancelHardwareAnimations(boolean jumpToEnd) {
-        final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
-        final int N = runningAnimations.size();
-        for (int i = 0; i < N; i++) {
-            if (jumpToEnd) {
-                runningAnimations.get(i).end();
-            } else {
-                runningAnimations.get(i).cancel();
-            }
-        }
-        runningAnimations.clear();
-
-        if (mHasPendingHardwareExit) {
-            // If we had a pending hardware exit, jump to the end state.
-            mHasPendingHardwareExit = false;
-
-            if (jumpToEnd) {
-                mOuterOpacity = 0;
-            }
-        }
-
-        mHardwareAnimating = false;
-    }
-
-    private void invalidateSelf() {
-        mOwner.invalidateSelf();
-    }
-
-    private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() {
+    private static final BackgroundProperty OPACITY = new BackgroundProperty("opacity") {
         @Override
-        public void onAnimationEnd(Animator animation) {
-            mHardwareAnimating = false;
+        public void setValue(RippleBackground object, float value) {
+            object.mOpacity = value;
+            object.invalidateSelf();
+        }
+
+        @Override
+        public Float get(RippleBackground object) {
+            return object.mOpacity;
         }
     };
 }
diff --git a/graphics/java/android/graphics/drawable/RippleComponent.java b/graphics/java/android/graphics/drawable/RippleComponent.java
new file mode 100644
index 0000000..fd3e06c
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/RippleComponent.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2015 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.graphics.drawable;
+
+import android.animation.Animator;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.view.HardwareCanvas;
+import android.view.RenderNodeAnimator;
+
+import java.util.ArrayList;
+
+/**
+ * Abstract class that handles hardware/software hand-off and lifecycle for
+ * animated ripple foreground and background components.
+ */
+abstract class RippleComponent {
+    private final RippleDrawable mOwner;
+
+    /** Bounds used for computing max radius. May be modified by the owner. */
+    protected final Rect mBounds;
+
+    /** Whether we can use hardware acceleration for the exit animation. */
+    private boolean mHasHardwareCanvas;
+
+    private boolean mHasPendingHardwareAnimator;
+    private RenderNodeAnimatorSet mHardwareAnimator;
+
+    private Animator mSoftwareAnimator;
+
+    /** Whether we have an explicit maximum radius. */
+    private boolean mHasMaxRadius;
+
+    /** How big this ripple should be when fully entered. */
+    protected float mTargetRadius;
+
+    /** Screen density used to adjust pixel-based constants. */
+    protected float mDensity;
+
+    public RippleComponent(RippleDrawable owner, Rect bounds) {
+        mOwner = owner;
+        mBounds = bounds;
+    }
+
+    public final void setup(float maxRadius, float density) {
+        if (maxRadius >= 0) {
+            mHasMaxRadius = true;
+            mTargetRadius = maxRadius;
+        } else {
+            final float halfWidth = mBounds.width() / 2.0f;
+            final float halfHeight = mBounds.height() / 2.0f;
+            mTargetRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
+        }
+
+        mDensity = density;
+
+        onSetup();
+        onTargetRadiusChanged(mTargetRadius);
+    }
+
+    /**
+     * Starts a ripple enter animation.
+     *
+     * @param fast whether the ripple should enter quickly
+     */
+    public final void enter(boolean fast) {
+        cancel();
+
+        mSoftwareAnimator = createSoftwareEnter(fast);
+        mSoftwareAnimator.start();
+    }
+
+    /**
+     * Starts a ripple exit animation.
+     */
+    public final void exit() {
+        cancel();
+
+        if (mHasHardwareCanvas) {
+            // We don't have access to a canvas here, but we expect one on the
+            // next frame. We'll start the render thread animation then.
+            mHasPendingHardwareAnimator = true;
+
+            // Request another frame.
+            invalidateSelf();
+        } else {
+            mSoftwareAnimator = createSoftwareExit();
+            mSoftwareAnimator.start();
+        }
+    }
+
+    /**
+     * Cancels all animations. Software animation values are left in the
+     * current state, while hardware animation values jump to the end state.
+     */
+    public void cancel() {
+        cancelSoftwareAnimations();
+        endHardwareAnimations();
+    }
+
+    /**
+     * Ends all animations, jumping values to the end state.
+     */
+    public void end() {
+        endSoftwareAnimations();
+        endHardwareAnimations();
+    }
+
+    /**
+     * Draws the ripple to the canvas, inheriting the paint's color and alpha
+     * properties.
+     *
+     * @param c the canvas to which the ripple should be drawn
+     * @param p the paint used to draw the ripple
+     * @return {@code true} if something was drawn, {@code false} otherwise
+     */
+    public boolean draw(Canvas c, Paint p) {
+        final boolean hasHardwareCanvas = c.isHardwareAccelerated()
+                && c instanceof HardwareCanvas;
+        if (mHasHardwareCanvas != hasHardwareCanvas) {
+            mHasHardwareCanvas = hasHardwareCanvas;
+
+            if (!hasHardwareCanvas) {
+                // We've switched from hardware to non-hardware mode. Panic.
+                endHardwareAnimations();
+            }
+        }
+
+        if (hasHardwareCanvas) {
+            final HardwareCanvas hw = (HardwareCanvas) c;
+            startPendingAnimation(hw, p);
+
+            if (mHardwareAnimator != null) {
+                return drawHardware(hw);
+            }
+        }
+
+        return drawSoftware(c, p);
+    }
+
+    /**
+     * Populates {@code bounds} with the maximum drawing bounds of the ripple
+     * relative to its center. The resulting bounds should be translated into
+     * parent drawable coordinates before use.
+     *
+     * @param bounds the rect to populate with drawing bounds
+     */
+    public void getBounds(Rect bounds) {
+        final int r = (int) Math.ceil(mTargetRadius);
+        bounds.set(-r, -r, r, r);
+    }
+
+    /**
+     * Starts the pending hardware animation, if available.
+     *
+     * @param hw hardware canvas on which the animation should draw
+     * @param p paint whose properties the hardware canvas should use
+     */
+    private void startPendingAnimation(HardwareCanvas hw, Paint p) {
+        if (mHasPendingHardwareAnimator) {
+            mHasPendingHardwareAnimator = false;
+
+            mHardwareAnimator = createHardwareExit(new Paint(p));
+            mHardwareAnimator.start(hw);
+
+            // Preemptively jump the software values to the end state now that
+            // the hardware exit has read whatever values it needs.
+            jumpValuesToExit();
+        }
+    }
+
+    /**
+     * Cancels any current software animations, leaving the values in their
+     * current state.
+     */
+    private void cancelSoftwareAnimations() {
+        if (mSoftwareAnimator != null) {
+            mSoftwareAnimator.cancel();
+        }
+    }
+
+    /**
+     * Ends any current software animations, jumping the values to their end
+     * state.
+     */
+    private void endSoftwareAnimations() {
+        if (mSoftwareAnimator != null) {
+            mSoftwareAnimator.end();
+        }
+    }
+
+    /**
+     * Ends any pending or current hardware animations.
+     * <p>
+     * Hardware animations can't synchronize values back to the software
+     * thread, so there is no "cancel" equivalent.
+     */
+    private void endHardwareAnimations() {
+        if (mHardwareAnimator != null) {
+            mHardwareAnimator.end();
+            mHardwareAnimator = null;
+        }
+
+        if (mHasPendingHardwareAnimator) {
+            mHasPendingHardwareAnimator = false;
+        }
+    }
+
+    protected final void invalidateSelf() {
+        mOwner.invalidateSelf();
+    }
+
+    protected final boolean isHardwareAnimating() {
+        return mHardwareAnimator != null && mHardwareAnimator.isRunning()
+                || mHasPendingHardwareAnimator;
+    }
+
+    protected final void onHotspotBoundsChanged() {
+        if (!mHasMaxRadius) {
+            final float halfWidth = mBounds.width() / 2.0f;
+            final float halfHeight = mBounds.height() / 2.0f;
+            final float targetRadius = (float) Math.sqrt(halfWidth * halfWidth
+                    + halfHeight * halfHeight);
+
+            onTargetRadiusChanged(targetRadius);
+        }
+    }
+
+    /**
+     * Called when the target radius changes.
+     *
+     * @param targetRadius the new target radius
+     */
+    protected void onTargetRadiusChanged(float targetRadius) {
+        // Stub.
+    }
+
+    /**
+     * Called during ripple setup, which occurs before the first enter
+     * animation.
+     */
+    protected void onSetup() {
+        // Stub.
+    }
+
+    protected abstract Animator createSoftwareEnter(boolean fast);
+
+    protected abstract Animator createSoftwareExit();
+
+    protected abstract RenderNodeAnimatorSet createHardwareExit(Paint p);
+
+    protected abstract boolean drawHardware(HardwareCanvas c);
+
+    protected abstract boolean drawSoftware(Canvas c, Paint p);
+
+    /**
+     * Called when the hardware exit is cancelled. Jumps software values to end
+     * state to ensure that software and hardware values are synchronized.
+     */
+    protected abstract void jumpValuesToExit();
+
+    public static class RenderNodeAnimatorSet {
+        private final ArrayList<RenderNodeAnimator> mAnimators = new ArrayList<>();
+
+        public void add(RenderNodeAnimator anim) {
+            mAnimators.add(anim);
+        }
+
+        public void clear() {
+            mAnimators.clear();
+        }
+
+        public void start(HardwareCanvas target) {
+            if (target == null) {
+                throw new IllegalArgumentException("Hardware canvas must be non-null");
+            }
+
+            final ArrayList<RenderNodeAnimator> animators = mAnimators;
+            final int N = animators.size();
+            for (int i = 0; i < N; i++) {
+                final RenderNodeAnimator anim = animators.get(i);
+                anim.setTarget(target);
+                anim.start();
+            }
+        }
+
+        public void cancel() {
+            final ArrayList<RenderNodeAnimator> animators = mAnimators;
+            final int N = animators.size();
+            for (int i = 0; i < N; i++) {
+                final RenderNodeAnimator anim = animators.get(i);
+                anim.cancel();
+            }
+        }
+
+        public void end() {
+            final ArrayList<RenderNodeAnimator> animators = mAnimators;
+            final int N = animators.size();
+            for (int i = 0; i < N; i++) {
+                final RenderNodeAnimator anim = animators.get(i);
+                anim.end();
+            }
+        }
+
+        public boolean isRunning() {
+            final ArrayList<RenderNodeAnimator> animators = mAnimators;
+            final int N = animators.size();
+            for (int i = 0; i < N; i++) {
+                final RenderNodeAnimator anim = animators.get(i);
+                if (anim.isRunning()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+}
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index b028eeb..bc8c7d2 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -137,7 +137,7 @@
     private boolean mBackgroundActive;
 
     /** The current ripple. May be actively animating or pending entry. */
-    private Ripple mRipple;
+    private RippleForeground mRipple;
 
     /** Whether we expect to draw a ripple when visible. */
     private boolean mRippleActive;
@@ -151,7 +151,7 @@
      * Lazily-created array of actively animating ripples. Inactive ripples are
      * pruned during draw(). The locations of these will not change.
      */
-    private Ripple[] mExitingRipples;
+    private RippleForeground[] mExitingRipples;
     private int mExitingRipplesCount = 0;
 
     /** Paint used to control appearance of ripples. */
@@ -204,11 +204,11 @@
         super.jumpToCurrentState();
 
         if (mRipple != null) {
-            mRipple.jump();
+            mRipple.end();
         }
 
         if (mBackground != null) {
-            mBackground.jump();
+            mBackground.end();
         }
 
         cancelExitingRipples();
@@ -219,10 +219,13 @@
         boolean needsDraw = false;
 
         final int count = mExitingRipplesCount;
-        final Ripple[] ripples = mExitingRipples;
+        final RippleForeground[] ripples = mExitingRipples;
         for (int i = 0; i < count; i++) {
+            // If the ripple is animating on the hardware thread, we'll need to
+            // draw an additional frame after canceling to restore the software
+            // drawing path.
             needsDraw |= ripples[i].isHardwareAnimating();
-            ripples[i].cancel();
+            ripples[i].end();
         }
 
         if (ripples != null) {
@@ -264,11 +267,9 @@
         for (int state : stateSet) {
             if (state == R.attr.state_enabled) {
                 enabled = true;
-            }
-            if (state == R.attr.state_focused) {
+            } else if (state == R.attr.state_focused) {
                 focused = true;
-            }
-            if (state == R.attr.state_pressed) {
+            } else if (state == R.attr.state_pressed) {
                 pressed = true;
             }
         }
@@ -563,11 +564,11 @@
                 x = mHotspotBounds.exactCenterX();
                 y = mHotspotBounds.exactCenterY();
             }
-            mRipple = new Ripple(this, mHotspotBounds, x, y);
+            mRipple = new RippleForeground(this, mHotspotBounds, x, y);
         }
 
         mRipple.setup(mState.mMaxRadius, mDensity);
-        mRipple.enter();
+        mRipple.enter(false);
     }
 
     /**
@@ -577,7 +578,7 @@
     private void tryRippleExit() {
         if (mRipple != null) {
             if (mExitingRipples == null) {
-                mExitingRipples = new Ripple[MAX_RIPPLES];
+                mExitingRipples = new RippleForeground[MAX_RIPPLES];
             }
             mExitingRipples[mExitingRipplesCount++] = mRipple;
             mRipple.exit();
@@ -591,13 +592,13 @@
      */
     private void clearHotspots() {
         if (mRipple != null) {
-            mRipple.cancel();
+            mRipple.end();
             mRipple = null;
             mRippleActive = false;
         }
 
         if (mBackground != null) {
-            mBackground.cancel();
+            mBackground.end();
             mBackground = null;
             mBackgroundActive = false;
         }
@@ -624,7 +625,7 @@
      */
     private void onHotspotBoundsChanged() {
         final int count = mExitingRipplesCount;
-        final Ripple[] ripples = mExitingRipples;
+        final RippleForeground[] ripples = mExitingRipples;
         for (int i = 0; i < count; i++) {
             ripples[i].onHotspotBoundsChanged();
         }
@@ -662,6 +663,8 @@
      */
     @Override
     public void draw(@NonNull Canvas canvas) {
+        pruneRipples();
+
         // Clip to the dirty bounds, which will be the drawable bounds if we
         // have a mask or content and the ripple bounds if we're projecting.
         final Rect bounds = getDirtyBounds();
@@ -682,6 +685,26 @@
         mHasValidMask = false;
     }
 
+    private void pruneRipples() {
+        int remaining = 0;
+
+        // Move remaining entries into pruned spaces.
+        final RippleForeground[] ripples = mExitingRipples;
+        final int count = mExitingRipplesCount;
+        for (int i = 0; i < count; i++) {
+            if (!ripples[i].hasFinishedExit()) {
+                ripples[remaining++] = ripples[i];
+            }
+        }
+
+        // Null out the remaining entries.
+        for (int i = remaining; i < count; i++) {
+            ripples[i] = null;
+        }
+
+        mExitingRipplesCount = remaining;
+    }
+
     /**
      * @return whether we need to use a mask
      */
@@ -747,7 +770,7 @@
 
     private int getMaskType() {
         if (mRipple == null && mExitingRipplesCount <= 0
-                && (mBackground == null || !mBackground.shouldDraw())) {
+                && (mBackground == null || !mBackground.isVisible())) {
             // We might need a mask later.
             return MASK_UNKNOWN;
         }
@@ -774,36 +797,6 @@
         return MASK_NONE;
     }
 
-    /**
-     * Removes a ripple from the exiting ripple list.
-     *
-     * @param ripple the ripple to remove
-     */
-    void removeRipple(Ripple ripple) {
-        // Ripple ripple ripple ripple. Ripple ripple.
-        final Ripple[] ripples = mExitingRipples;
-        final int count = mExitingRipplesCount;
-        final int index = getRippleIndex(ripple);
-        if (index >= 0) {
-            System.arraycopy(ripples, index + 1, ripples, index, count - (index + 1));
-            ripples[count - 1] = null;
-            mExitingRipplesCount--;
-
-            invalidateSelf();
-        }
-    }
-
-    private int getRippleIndex(Ripple ripple) {
-        final Ripple[] ripples = mExitingRipples;
-        final int count = mExitingRipplesCount;
-        for (int i = 0; i < count; i++) {
-            if (ripples[i] == ripple) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
     private void drawContent(Canvas canvas) {
         // Draw everything except the mask.
         final ChildDrawable[] array = mLayerState.mChildren;
@@ -816,10 +809,10 @@
     }
 
     private void drawBackgroundAndRipples(Canvas canvas) {
-        final Ripple active = mRipple;
+        final RippleForeground active = mRipple;
         final RippleBackground background = mBackground;
         final int count = mExitingRipplesCount;
-        if (active == null && count <= 0 && (background == null || !background.shouldDraw())) {
+        if (active == null && count <= 0 && (background == null || !background.isVisible())) {
             // Move along, nothing to draw here.
             return;
         }
@@ -859,12 +852,12 @@
             p.setShader(null);
         }
 
-        if (background != null && background.shouldDraw()) {
+        if (background != null && background.isVisible()) {
             background.draw(canvas, p);
         }
 
         if (count > 0) {
-            final Ripple[] ripples = mExitingRipples;
+            final RippleForeground[] ripples = mExitingRipples;
             for (int i = 0; i < count; i++) {
                 ripples[i].draw(canvas, p);
             }
@@ -902,7 +895,7 @@
             final int cY = (int) mHotspotBounds.exactCenterY();
             final Rect rippleBounds = mTempRect;
 
-            final Ripple[] activeRipples = mExitingRipples;
+            final RippleForeground[] activeRipples = mExitingRipples;
             final int N = mExitingRipplesCount;
             for (int i = 0; i < N; i++) {
                 activeRipples[i].getBounds(rippleBounds);
diff --git a/graphics/java/android/graphics/drawable/RippleForeground.java b/graphics/java/android/graphics/drawable/RippleForeground.java
new file mode 100644
index 0000000..2023f04
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/RippleForeground.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2015 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.graphics.drawable;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.graphics.Canvas;
+import android.graphics.CanvasProperty;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.FloatProperty;
+import android.util.MathUtils;
+import android.view.HardwareCanvas;
+import android.view.RenderNodeAnimator;
+import android.view.animation.LinearInterpolator;
+
+/**
+ * Draws a ripple foreground.
+ */
+class RippleForeground extends RippleComponent {
+    private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
+    private static final TimeInterpolator DECELERATE_INTERPOLATOR = new LogDecelerateInterpolator(
+            400f, 1.4f, 0);
+
+    // Pixel-based accelerations and velocities.
+    private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024;
+    private static final float WAVE_TOUCH_UP_ACCELERATION = 3400;
+    private static final float WAVE_OPACITY_DECAY_VELOCITY = 3;
+
+    private static final int RIPPLE_ENTER_DELAY = 80;
+    private static final int OPACITY_ENTER_DURATION_FAST = 120;
+
+    private float mStartingX;
+    private float mStartingY;
+    private float mClampedStartingX;
+    private float mClampedStartingY;
+
+    // Hardware rendering properties.
+    private CanvasProperty<Paint> mPropPaint;
+    private CanvasProperty<Float> mPropRadius;
+    private CanvasProperty<Float> mPropX;
+    private CanvasProperty<Float> mPropY;
+
+    // Software rendering properties.
+    private float mOpacity = 1;
+    private float mOuterX;
+    private float mOuterY;
+
+    // Values used to tween between the start and end positions.
+    private float mTweenRadius = 0;
+    private float mTweenX = 0;
+    private float mTweenY = 0;
+
+    /** Whether this ripple has finished its exit animation. */
+    private boolean mHasFinishedExit;
+
+    public RippleForeground(RippleDrawable owner, Rect bounds, float startingX, float startingY) {
+        super(owner, bounds);
+
+        mStartingX = startingX;
+        mStartingY = startingY;
+    }
+
+    @Override
+    public void onSetup() {
+        mOuterX = 0;
+        mOuterY = 0;
+    }
+
+    @Override
+    protected void onTargetRadiusChanged(float targetRadius) {
+        clampStartingPosition();
+    }
+
+    @Override
+    protected boolean drawSoftware(Canvas c, Paint p) {
+        boolean hasContent = false;
+
+        final int origAlpha = p.getAlpha();
+        final int alpha = (int) (origAlpha * mOpacity + 0.5f);
+        final float radius = MathUtils.lerp(0, mTargetRadius, mTweenRadius);
+        if (alpha > 0 && radius > 0) {
+            final float x = MathUtils.lerp(
+                    mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
+            final float y = MathUtils.lerp(
+                    mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
+            p.setAlpha(alpha);
+            c.drawCircle(x, y, radius, p);
+            p.setAlpha(origAlpha);
+            hasContent = true;
+        }
+
+        return hasContent;
+    }
+
+    @Override
+    protected boolean drawHardware(HardwareCanvas c) {
+        c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
+        return true;
+    }
+
+    /**
+     * Returns the maximum bounds of the ripple relative to the ripple center.
+     */
+    public void getBounds(Rect bounds) {
+        final int outerX = (int) mOuterX;
+        final int outerY = (int) mOuterY;
+        final int r = (int) mTargetRadius + 1;
+        bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
+    }
+
+    /**
+     * Specifies the starting position relative to the drawable bounds. No-op if
+     * the ripple has already entered.
+     */
+    public void move(float x, float y) {
+        mStartingX = x;
+        mStartingY = y;
+
+        clampStartingPosition();
+    }
+
+    /**
+     * @return {@code true} if this ripple has finished its exit animation
+     */
+    public boolean hasFinishedExit() {
+        return mHasFinishedExit;
+    }
+
+    @Override
+    protected Animator createSoftwareEnter(boolean fast) {
+        final int duration = (int)
+                (1000 * Math.sqrt(mTargetRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);
+
+        final ObjectAnimator tweenAll = ObjectAnimator.ofFloat(this, TWEEN_ALL, 1);
+        tweenAll.setAutoCancel(true);
+        tweenAll.setDuration(duration);
+        tweenAll.setInterpolator(LINEAR_INTERPOLATOR);
+        tweenAll.setStartDelay(RIPPLE_ENTER_DELAY);
+
+        final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
+        opacity.setAutoCancel(true);
+        opacity.setDuration(OPACITY_ENTER_DURATION_FAST);
+        opacity.setInterpolator(LINEAR_INTERPOLATOR);
+
+        final AnimatorSet set = new AnimatorSet();
+        set.play(tweenAll).with(opacity);
+
+        return set;
+    }
+
+    private int getRadiusExitDuration() {
+        final float radius = MathUtils.lerp(0, mTargetRadius, mTweenRadius);
+        final float remaining = mTargetRadius - radius;
+        return (int) (1000 * Math.sqrt(remaining / (WAVE_TOUCH_UP_ACCELERATION
+                + WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5);
+    }
+
+    private int getOpacityExitDuration() {
+        return (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
+    }
+
+    @Override
+    protected Animator createSoftwareExit() {
+        final int radiusDuration = getRadiusExitDuration();
+        final int opacityDuration = getOpacityExitDuration();
+
+        final ObjectAnimator tweenAll = ObjectAnimator.ofFloat(this, TWEEN_ALL, 1);
+        tweenAll.setAutoCancel(true);
+        tweenAll.setDuration(radiusDuration);
+        tweenAll.setInterpolator(DECELERATE_INTERPOLATOR);
+
+        final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 0);
+        opacity.setAutoCancel(true);
+        opacity.setDuration(opacityDuration);
+        opacity.setInterpolator(LINEAR_INTERPOLATOR);
+
+        final AnimatorSet set = new AnimatorSet();
+        set.play(tweenAll).with(opacity);
+        set.addListener(mAnimationListener);
+
+        return set;
+    }
+
+    @Override
+    protected RenderNodeAnimatorSet createHardwareExit(Paint p) {
+        final int radiusDuration = getRadiusExitDuration();
+        final int opacityDuration = getOpacityExitDuration();
+
+        final float startX = MathUtils.lerp(
+                mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
+        final float startY = MathUtils.lerp(
+                mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
+
+        final float startRadius = MathUtils.lerp(0, mTargetRadius, mTweenRadius);
+        p.setAlpha((int) (p.getAlpha() * mOpacity + 0.5f));
+
+        mPropPaint = CanvasProperty.createPaint(p);
+        mPropRadius = CanvasProperty.createFloat(startRadius);
+        mPropX = CanvasProperty.createFloat(startX);
+        mPropY = CanvasProperty.createFloat(startY);
+
+        final RenderNodeAnimator radius = new RenderNodeAnimator(mPropRadius, mTargetRadius);
+        radius.setDuration(radiusDuration);
+        radius.setInterpolator(DECELERATE_INTERPOLATOR);
+
+        final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mOuterX);
+        x.setDuration(radiusDuration);
+        x.setInterpolator(DECELERATE_INTERPOLATOR);
+
+        final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mOuterY);
+        y.setDuration(radiusDuration);
+        y.setInterpolator(DECELERATE_INTERPOLATOR);
+
+        final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
+                RenderNodeAnimator.PAINT_ALPHA, 0);
+        opacity.setDuration(opacityDuration);
+        opacity.setInterpolator(LINEAR_INTERPOLATOR);
+        opacity.addListener(mAnimationListener);
+
+        final RenderNodeAnimatorSet set = new RenderNodeAnimatorSet();
+        set.add(radius);
+        set.add(opacity);
+        set.add(x);
+        set.add(y);
+
+        return set;
+    }
+
+    @Override
+    protected void jumpValuesToExit() {
+        mOpacity = 0;
+        mTweenX = 1;
+        mTweenY = 1;
+        mTweenRadius = 1;
+    }
+
+    /**
+     * Clamps the starting position to fit within the ripple bounds.
+     */
+    private void clampStartingPosition() {
+        final float cX = mBounds.exactCenterX();
+        final float cY = mBounds.exactCenterY();
+        final float dX = mStartingX - cX;
+        final float dY = mStartingY - cY;
+        final float r = mTargetRadius;
+        if (dX * dX + dY * dY > r * r) {
+            // Point is outside the circle, clamp to the perimeter.
+            final double angle = Math.atan2(dY, dX);
+            mClampedStartingX = cX + (float) (Math.cos(angle) * r);
+            mClampedStartingY = cY + (float) (Math.sin(angle) * r);
+        } else {
+            mClampedStartingX = mStartingX;
+            mClampedStartingY = mStartingY;
+        }
+    }
+
+    private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animator) {
+            mHasFinishedExit = true;
+        }
+    };
+
+    /**
+    * Interpolator with a smooth log deceleration.
+    */
+    private static final class LogDecelerateInterpolator implements TimeInterpolator {
+        private final float mBase;
+        private final float mDrift;
+        private final float mTimeScale;
+        private final float mOutputScale;
+
+        public LogDecelerateInterpolator(float base, float timeScale, float drift) {
+            mBase = base;
+            mDrift = drift;
+            mTimeScale = 1f / timeScale;
+
+            mOutputScale = 1f / computeLog(1f);
+        }
+
+        private float computeLog(float t) {
+            return 1f - (float) Math.pow(mBase, -t * mTimeScale) + (mDrift * t);
+        }
+
+        @Override
+        public float getInterpolation(float t) {
+            return computeLog(t) * mOutputScale;
+        }
+    }
+
+    /**
+     * Property for animating radius, center X, and center Y between their
+     * initial and target values.
+     */
+    private static final FloatProperty<RippleForeground> TWEEN_ALL =
+            new FloatProperty<RippleForeground>("tweenAll") {
+        @Override
+        public void setValue(RippleForeground object, float value) {
+            object.mTweenRadius = value;
+            object.mTweenX = value;
+            object.mTweenY = value;
+            object.invalidateSelf();
+        }
+
+        @Override
+        public Float get(RippleForeground object) {
+            return object.mTweenRadius;
+        }
+    };
+
+    /**
+     * Property for animating opacity between 0 and its target value.
+     */
+    private static final FloatProperty<RippleForeground> OPACITY =
+            new FloatProperty<RippleForeground>("opacity") {
+        @Override
+        public void setValue(RippleForeground object, float value) {
+            object.mOpacity = value;
+            object.invalidateSelf();
+        }
+
+        @Override
+        public Float get(RippleForeground object) {
+            return object.mOpacity;
+        }
+    };
+}
diff --git a/include/android_runtime/android_view_Surface.h b/include/android_runtime/android_view_Surface.h
index 53e8b49..a6836a8 100644
--- a/include/android_runtime/android_view_Surface.h
+++ b/include/android_runtime/android_view_Surface.h
@@ -26,6 +26,33 @@
 class Surface;
 class IGraphicBufferProducer;
 
+/**
+ * Enum mirroring the public API definitions for image and pixel formats.
+ * Some of these are hidden in the public API
+ *
+ * Keep up to date with android.graphics.ImageFormat and
+ * android.graphics.PixelFormat
+ */
+enum class PublicFormat {
+    UNKNOWN           = 0x0,
+    RGBA_8888         = 0x1,
+    RGBX_8888         = 0x2,
+    RGB_888           = 0x3,
+    RGB_565           = 0x4,
+    NV16              = 0x10,
+    NV21              = 0x11,
+    YUY2              = 0x14,
+    RAW_SENSOR        = 0x20,
+    YUV_420_888       = 0x23,
+    RAW10             = 0x25,
+    JPEG              = 0x100,
+    DEPTH_POINT_CLOUD = 0x101,
+    YV12              = 0x32315659,
+    Y8                = 0x20203859, // @hide
+    Y16               = 0x20363159, // @hide
+    DEPTH16           = 0x44363159
+};
+
 /* Gets the underlying ANativeWindow for a Surface. */
 extern sp<ANativeWindow> android_view_Surface_getNativeWindow(
         JNIEnv* env, jobject surfaceObj);
@@ -40,6 +67,21 @@
 extern jobject android_view_Surface_createFromIGraphicBufferProducer(JNIEnv* env,
         const sp<IGraphicBufferProducer>& bufferProducer);
 
+/* Convert from android.graphics.ImageFormat/PixelFormat enums to graphics.h HAL
+ * format */
+extern int android_view_Surface_mapPublicFormatToHalFormat(PublicFormat f);
+
+/* Convert from android.graphics.ImageFormat/PixelFormat enums to graphics.h HAL
+ * dataspace */
+extern android_dataspace android_view_Surface_mapPublicFormatToHalDataspace(
+        PublicFormat f);
+
+/* Convert from HAL format, dataspace pair to
+ * android.graphics.ImageFormat/PixelFormat.
+ * For unknown/unspecified pairs, returns PublicFormat::UNKNOWN */
+extern PublicFormat android_view_Surface_mapHalFormatDataspaceToPublicFormat(
+        int format, android_dataspace dataSpace);
+
 } // namespace android
 
 #endif // _ANDROID_VIEW_SURFACE_H
diff --git a/libs/hwui/Glop.h b/libs/hwui/Glop.h
index 62da6e08..2c6f6c1 100644
--- a/libs/hwui/Glop.h
+++ b/libs/hwui/Glop.h
@@ -78,7 +78,7 @@
         // TODO: enforce mutual exclusion with restricted setters and/or unions
         struct Vertices {
             GLuint bufferObject;
-            VertexAttribFlags flags;
+            int attribFlags;
             const void* position;
             const void* texCoord;
             const void* color;
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index 711b11c..1d07951 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -66,7 +66,7 @@
     mOutGlop->mesh.indices = { 0, nullptr };
     mOutGlop->mesh.vertices = {
             mRenderState.meshState().getUnitQuadVBO(),
-            VertexAttribFlags::kNone,
+            static_cast<int>(VertexAttribFlags::kNone),
             nullptr, nullptr, nullptr,
             kTextureVertexStride };
     mOutGlop->mesh.elementCount = 4;
@@ -85,7 +85,7 @@
     mOutGlop->mesh.indices = { 0, nullptr };
     mOutGlop->mesh.vertices = {
             mRenderState.meshState().getUnitQuadVBO(),
-            VertexAttribFlags::kTextureCoord,
+            static_cast<int>(VertexAttribFlags::kTextureCoord),
             nullptr, (const void*) kMeshTextureOffset, nullptr,
             kTextureVertexStride };
     mOutGlop->mesh.elementCount = 4;
@@ -105,7 +105,7 @@
     mOutGlop->mesh.indices = { 0, nullptr };
     mOutGlop->mesh.vertices = {
             0,
-            VertexAttribFlags::kTextureCoord,
+            static_cast<int>(VertexAttribFlags::kTextureCoord),
             &textureVertex[0].x, &textureVertex[0].u, nullptr,
             kTextureVertexStride };
     mOutGlop->mesh.elementCount = 4;
@@ -119,7 +119,7 @@
     mOutGlop->mesh.indices = { mRenderState.meshState().getQuadListIBO(), nullptr };
     mOutGlop->mesh.vertices = {
             0,
-            VertexAttribFlags::kNone,
+            static_cast<int>(VertexAttribFlags::kNone),
             vertexData, nullptr, nullptr,
             kVertexStride };
     mOutGlop->mesh.elementCount = 6 * quadCount;
@@ -133,7 +133,7 @@
     mOutGlop->mesh.indices = { mRenderState.meshState().getQuadListIBO(), nullptr };
     mOutGlop->mesh.vertices = {
             0,
-            VertexAttribFlags::kTextureCoord,
+            static_cast<int>(VertexAttribFlags::kTextureCoord),
             &vertexData[0].x, &vertexData[0].u, nullptr,
             kTextureVertexStride };
     mOutGlop->mesh.elementCount = elementCount;
@@ -147,7 +147,7 @@
     mOutGlop->mesh.indices = { 0, nullptr };
     mOutGlop->mesh.vertices = {
             0,
-            VertexAttribFlags::kTextureCoord,
+            static_cast<int>(VertexAttribFlags::kTextureCoord),
             &vertexData[0].x, &vertexData[0].u, nullptr,
             kTextureVertexStride };
     mOutGlop->mesh.elementCount = elementCount;
@@ -161,7 +161,7 @@
     mOutGlop->mesh.indices = { 0, nullptr };
     mOutGlop->mesh.vertices = {
             0,
-            static_cast<VertexAttribFlags>(VertexAttribFlags::kTextureCoord | VertexAttribFlags::kColor),
+            VertexAttribFlags::kTextureCoord | VertexAttribFlags::kColor,
             &vertexData[0].x, &vertexData[0].u, &vertexData[0].r,
             kColorTextureVertexStride };
     mOutGlop->mesh.elementCount = elementCount;
@@ -180,7 +180,7 @@
     mOutGlop->mesh.indices = { 0, vertexBuffer.getIndices() };
     mOutGlop->mesh.vertices = {
             0,
-            alphaVertex ? VertexAttribFlags::kAlpha : VertexAttribFlags::kNone,
+            static_cast<int>(alphaVertex ? VertexAttribFlags::kAlpha : VertexAttribFlags::kNone),
             vertexBuffer.getBuffer(), nullptr, nullptr,
             alphaVertex ? kAlphaVertexStride : kVertexStride };
     mOutGlop->mesh.elementCount = indices
@@ -197,7 +197,7 @@
     mOutGlop->mesh.indices = { mRenderState.meshState().getQuadListIBO(), nullptr };
     mOutGlop->mesh.vertices = {
             mCaches.patchCache.getMeshBuffer(),
-            VertexAttribFlags::kTextureCoord,
+            static_cast<int>(VertexAttribFlags::kTextureCoord),
             (void*)patch.positionOffset, (void*)patch.textureOffset, nullptr,
             kTextureVertexStride };
     mOutGlop->mesh.elementCount = patch.indexCount;
@@ -230,7 +230,7 @@
 
     mOutGlop->blend = { GL_ZERO, GL_ZERO };
     if (mOutGlop->fill.color.a < 1.0f
-            || (mOutGlop->mesh.vertices.flags & VertexAttribFlags::kAlpha)
+            || (mOutGlop->mesh.vertices.attribFlags & VertexAttribFlags::kAlpha)
             || (mOutGlop->fill.texture.texture && mOutGlop->fill.texture.texture->blend)
             || mOutGlop->roundRectClipState
             || PaintUtils::isBlendedShader(shader)
@@ -324,7 +324,7 @@
 
         const bool SWAP_SRC_DST = false;
         if (alphaScale < 1.0f
-                || (mOutGlop->mesh.vertices.flags & VertexAttribFlags::kAlpha)
+                || (mOutGlop->mesh.vertices.attribFlags & VertexAttribFlags::kAlpha)
                 || texture.blend
                 || mOutGlop->roundRectClipState) {
             Blend::getFactors(SkXfermode::kSrcOver_Mode, SWAP_SRC_DST,
@@ -540,12 +540,25 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 void verify(const ProgramDescription& description, const Glop& glop) {
-    bool hasTexture = glop.fill.texture.texture != nullptr;
-    LOG_ALWAYS_FATAL_IF(description.hasTexture && description.hasExternalTexture);
-    LOG_ALWAYS_FATAL_IF((description.hasTexture || description.hasExternalTexture )!= hasTexture);
-    LOG_ALWAYS_FATAL_IF((glop.mesh.vertices.flags & VertexAttribFlags::kTextureCoord) != hasTexture);
+    if (glop.fill.texture.texture != nullptr) {
+        LOG_ALWAYS_FATAL_IF(((description.hasTexture && description.hasExternalTexture)
+                        || (!description.hasTexture && !description.hasExternalTexture)
+                        || ((glop.mesh.vertices.attribFlags & VertexAttribFlags::kTextureCoord) == 0)),
+                "Texture %p, hT%d, hET %d, attribFlags %x",
+                glop.fill.texture.texture,
+                description.hasTexture, description.hasExternalTexture,
+                glop.mesh.vertices.attribFlags);
+    } else {
+        LOG_ALWAYS_FATAL_IF((description.hasTexture
+                        || description.hasExternalTexture
+                        || ((glop.mesh.vertices.attribFlags & VertexAttribFlags::kTextureCoord) != 0)),
+                "No texture, hT%d, hET %d, attribFlags %x",
+                description.hasTexture, description.hasExternalTexture,
+                glop.mesh.vertices.attribFlags);
+    }
 
-    if ((glop.mesh.vertices.flags & VertexAttribFlags::kAlpha) && glop.mesh.vertices.bufferObject) {
+    if ((glop.mesh.vertices.attribFlags & VertexAttribFlags::kAlpha)
+            && glop.mesh.vertices.bufferObject) {
         LOG_ALWAYS_FATAL("VBO and alpha attributes are not currently compatible");
     }
 
@@ -556,12 +569,12 @@
 
 void GlopBuilder::build() {
     REQUIRE_STAGES(kAllStages);
-    if (mOutGlop->mesh.vertices.flags & VertexAttribFlags::kTextureCoord) {
+    if (mOutGlop->mesh.vertices.attribFlags & VertexAttribFlags::kTextureCoord) {
         mDescription.hasTexture = mOutGlop->fill.texture.target == GL_TEXTURE_2D;
         mDescription.hasExternalTexture = mOutGlop->fill.texture.target == GL_TEXTURE_EXTERNAL_OES;
     }
-    mDescription.hasColors = mOutGlop->mesh.vertices.flags & VertexAttribFlags::kColor;
-    mDescription.hasVertexAlpha = mOutGlop->mesh.vertices.flags & VertexAttribFlags::kAlpha;
+    mDescription.hasColors = mOutGlop->mesh.vertices.attribFlags & VertexAttribFlags::kColor;
+    mDescription.hasVertexAlpha = mOutGlop->mesh.vertices.attribFlags & VertexAttribFlags::kAlpha;
 
     // serialize shader info into ShaderData
     GLuint textureUnit = mOutGlop->fill.texture.texture ? 1 : 0;
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index ca3a4c2..7b44d6d 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -259,7 +259,7 @@
     // indices
     meshState().bindIndicesBufferInternal(indices.bufferObject);
 
-    if (vertices.flags & VertexAttribFlags::kTextureCoord) {
+    if (vertices.attribFlags & VertexAttribFlags::kTextureCoord) {
         const Glop::Fill::TextureData& texture = fill.texture;
         // texture always takes slot 0, shader samplers increment from there
         mCaches->textureState().activateTexture(0);
@@ -283,13 +283,13 @@
         meshState().disableTexCoordsVertexArray();
     }
     int colorLocation = -1;
-    if (vertices.flags & VertexAttribFlags::kColor) {
+    if (vertices.attribFlags & VertexAttribFlags::kColor) {
         colorLocation = fill.program->getAttrib("colors");
         glEnableVertexAttribArray(colorLocation);
         glVertexAttribPointer(colorLocation, 4, GL_FLOAT, GL_FALSE, vertices.stride, vertices.color);
     }
     int alphaLocation = -1;
-    if (vertices.flags & VertexAttribFlags::kAlpha) {
+    if (vertices.attribFlags & VertexAttribFlags::kAlpha) {
         // NOTE: alpha vertex position is computed assuming no VBO
         const void* alphaCoords = ((const GLbyte*) vertices.position) + kVertexAlphaOffset;
         alphaLocation = fill.program->getAttrib("vtxAlpha");
@@ -317,7 +317,7 @@
 
             // rebind pointers without forcing, since initial bind handled above
             meshState().bindPositionVertexPointer(false, vertexData, vertices.stride);
-            if (vertices.flags & VertexAttribFlags::kTextureCoord) {
+            if (vertices.attribFlags & VertexAttribFlags::kTextureCoord) {
                 meshState().bindTexCoordsVertexPointer(false,
                         vertexData + kMeshTextureOffset, vertices.stride);
             }
@@ -335,10 +335,10 @@
     // -----------------------------------
     // ---------- Mesh teardown ----------
     // -----------------------------------
-    if (vertices.flags & VertexAttribFlags::kAlpha) {
+    if (vertices.attribFlags & VertexAttribFlags::kAlpha) {
         glDisableVertexAttribArray(alphaLocation);
     }
-    if (vertices.flags & VertexAttribFlags::kColor) {
+    if (vertices.attribFlags & VertexAttribFlags::kColor) {
         glDisableVertexAttribArray(colorLocation);
     }
 }
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 09f4bac..4526839 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -279,6 +279,7 @@
      * Internal use only
      * @return a combined mask of all flags
      */
+    @SystemApi
     public int getAllFlags() {
         return (mFlags & FLAG_ALL);
     }
@@ -541,14 +542,15 @@
         /**
          * @hide
          * Same as {@link #setCapturePreset(int)} but authorizes the use of HOTWORD,
-         * REMOTE_SUBMIX and FM_TUNER.
+         * REMOTE_SUBMIX and RADIO_TUNER.
          * @param preset
          * @return the same Builder instance.
          */
+        @SystemApi
         public Builder setInternalCapturePreset(int preset) {
             if ((preset == MediaRecorder.AudioSource.HOTWORD)
                     || (preset == MediaRecorder.AudioSource.REMOTE_SUBMIX)
-                    || (preset == MediaRecorder.AudioSource.FM_TUNER)) {
+                    || (preset == MediaRecorder.AudioSource.RADIO_TUNER)) {
                 mSource = preset;
             } else {
                 setCapturePreset(preset);
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index de10ef9..259fe37 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -20,6 +20,7 @@
 import java.nio.ByteBuffer;
 import java.util.Iterator;
 
+import android.annotation.SystemApi;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -238,7 +239,6 @@
 
     /**
      * @hide
-     * CANDIDATE FOR PUBLIC API
      * Class constructor with {@link AudioAttributes} and {@link AudioFormat}.
      * @param attributes a non-null {@link AudioAttributes} instance. Use
      *     {@link AudioAttributes.Builder#setCapturePreset(int)} for configuring the capture
@@ -257,6 +257,7 @@
      *   construction.
      * @throws IllegalArgumentException
      */
+    @SystemApi
     public AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
             int sessionId) throws IllegalArgumentException {
         mRecordingState = RECORDSTATE_STOPPED;
@@ -376,7 +377,7 @@
         // audio source
         if ( (audioSource < MediaRecorder.AudioSource.DEFAULT) ||
              ((audioSource > MediaRecorder.getAudioSourceMax()) &&
-              (audioSource != MediaRecorder.AudioSource.FM_TUNER) &&
+              (audioSource != MediaRecorder.AudioSource.RADIO_TUNER) &&
               (audioSource != MediaRecorder.AudioSource.HOTWORD)) )  {
             throw new IllegalArgumentException("Invalid audio source.");
         }
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index 8d6a588..824a7ad 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -483,6 +483,8 @@
             case ImageFormat.Y16:
             case ImageFormat.RAW_SENSOR:
             case ImageFormat.RAW10:
+            case ImageFormat.DEPTH16:
+            case ImageFormat.DEPTH_POINT_CLOUD:
                 return 1;
             default:
                 throw new UnsupportedOperationException(
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 97b3f63..58c86f2 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -16,6 +16,7 @@
 
 package android.media;
 
+import android.annotation.SystemApi;
 import android.app.ActivityThread;
 import android.hardware.Camera;
 import android.os.Handler;
@@ -222,12 +223,11 @@
         public static final int REMOTE_SUBMIX = 8;
 
         /**
-         * Audio source for FM, which is used to capture current FM tuner output by FMRadio app.
-         * There are two use cases, one is for record FM stream for later listening, another is
-         * for FM indirect mode(the routing except FM to headset(headphone) device routing).
+         * Audio source for capturing broadcast radio tuner output.
          * @hide
          */
-        public static final int FM_TUNER = 1998;
+        @SystemApi
+        public static final int RADIO_TUNER = 1998;
 
         /**
          * Audio source for preemptible, low-priority software hotword detection
@@ -240,7 +240,8 @@
          * This is a hidden audio source.
          * @hide
          */
-        protected static final int HOTWORD = 1999;
+        @SystemApi
+        public static final int HOTWORD = 1999;
     }
 
     /**
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index cf69b8f..b247493 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -95,6 +95,9 @@
     void setBufferFormat(int format) { mFormat = format; }
     int getBufferFormat() { return mFormat; }
 
+    void setBufferDataspace(android_dataspace dataSpace) { mDataSpace = dataSpace; }
+    android_dataspace getBufferDataspace() { return mDataSpace; }
+
     void setBufferWidth(int width) { mWidth = width; }
     int getBufferWidth() { return mWidth; }
 
@@ -111,6 +114,7 @@
     jobject mWeakThiz;
     jclass mClazz;
     int mFormat;
+    android_dataspace mDataSpace;
     int mWidth;
     int mHeight;
 };
@@ -263,29 +267,6 @@
     env->SetLongField(thiz, gSurfaceImageClassInfo.mLockedBuffer, reinterpret_cast<jlong>(buffer));
 }
 
-// Some formats like JPEG defined with different values between android.graphics.ImageFormat and
-// graphics.h, need convert to the one defined in graphics.h here.
-static int Image_getPixelFormat(JNIEnv* env, int format)
-{
-    int jpegFormat;
-    jfieldID fid;
-
-    ALOGV("%s: format = 0x%x", __FUNCTION__, format);
-
-    jclass imageFormatClazz = env->FindClass("android/graphics/ImageFormat");
-    ALOG_ASSERT(imageFormatClazz != NULL);
-
-    fid = env->GetStaticFieldID(imageFormatClazz, "JPEG", "I");
-    jpegFormat = env->GetStaticIntField(imageFormatClazz, fid);
-
-    // Translate the JPEG to BLOB for camera purpose.
-    if (format == jpegFormat) {
-        format = HAL_PIXEL_FORMAT_BLOB;
-    }
-
-    return format;
-}
-
 static uint32_t Image_getJpegSize(CpuConsumer::LockedBuffer* buffer, bool usingRGBAOverride)
 {
     ALOG_ASSERT(buffer != NULL, "Input buffer is NULL!!!");
@@ -483,7 +464,7 @@
 }
 
 static jint Image_imageGetPixelStride(JNIEnv* env, CpuConsumer::LockedBuffer* buffer, int idx,
-        int32_t readerFormat)
+        int32_t halReaderFormat)
 {
     ALOGV("%s: buffer index: %d", __FUNCTION__, idx);
     ALOG_ASSERT((idx < IMAGE_READER_MAX_NUM_PLANES) && (idx >= 0), "Index is out of range:%d", idx);
@@ -493,7 +474,7 @@
 
     int32_t fmt = buffer->flexFormat;
 
-    fmt = applyFormatOverrides(fmt, readerFormat);
+    fmt = applyFormatOverrides(fmt, halReaderFormat);
 
     switch (fmt) {
         case HAL_PIXEL_FORMAT_YCbCr_420_888:
@@ -543,7 +524,7 @@
 }
 
 static jint Image_imageGetRowStride(JNIEnv* env, CpuConsumer::LockedBuffer* buffer, int idx,
-        int32_t readerFormat)
+        int32_t halReaderFormat)
 {
     ALOGV("%s: buffer index: %d", __FUNCTION__, idx);
     ALOG_ASSERT((idx < IMAGE_READER_MAX_NUM_PLANES) && (idx >= 0));
@@ -553,7 +534,7 @@
 
     int32_t fmt = buffer->flexFormat;
 
-    fmt = applyFormatOverrides(fmt, readerFormat);
+    fmt = applyFormatOverrides(fmt, halReaderFormat);
 
     switch (fmt) {
         case HAL_PIXEL_FORMAT_YCbCr_420_888:
@@ -682,11 +663,16 @@
 {
     status_t res;
     int nativeFormat;
+    android_dataspace nativeDataspace;
 
     ALOGV("%s: width:%d, height: %d, format: 0x%x, maxImages:%d",
           __FUNCTION__, width, height, format, maxImages);
 
-    nativeFormat = Image_getPixelFormat(env, format);
+    PublicFormat publicFormat = static_cast<PublicFormat>(format);
+    nativeFormat = android_view_Surface_mapPublicFormatToHalFormat(
+        publicFormat);
+    nativeDataspace = android_view_Surface_mapPublicFormatToHalDataspace(
+        publicFormat);
 
     sp<IGraphicBufferProducer> gbProducer;
     sp<IGraphicBufferConsumer> gbConsumer;
@@ -710,10 +696,11 @@
     consumer->setFrameAvailableListener(ctx);
     ImageReader_setNativeContext(env, thiz, ctx);
     ctx->setBufferFormat(nativeFormat);
+    ctx->setBufferDataspace(nativeDataspace);
     ctx->setBufferWidth(width);
     ctx->setBufferHeight(height);
 
-    // Set the width/height/format to the CpuConsumer
+    // Set the width/height/format/dataspace to the CpuConsumer
     res = consumer->setDefaultBufferSize(width, height);
     if (res != OK) {
         jniThrowException(env, "java/lang/IllegalStateException",
@@ -725,6 +712,12 @@
         jniThrowException(env, "java/lang/IllegalStateException",
                           "Failed to set CpuConsumer buffer format");
     }
+    res = consumer->setDefaultBufferDataSpace(nativeDataspace);
+    if (res != OK) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+                          "Failed to set CpuConsumer buffer dataSpace");
+    }
+
 }
 
 static void ImageReader_close(JNIEnv* env, jobject thiz)
@@ -884,6 +877,8 @@
 static jobject Image_createSurfacePlane(JNIEnv* env, jobject thiz, int idx, int readerFormat)
 {
     int rowStride, pixelStride;
+    PublicFormat publicReaderFormat = static_cast<PublicFormat>(readerFormat);
+
     ALOGV("%s: buffer index: %d", __FUNCTION__, idx);
 
     CpuConsumer::LockedBuffer* buffer = Image_getLockedBuffer(env, thiz);
@@ -893,10 +888,11 @@
         jniThrowException(env, "java/lang/IllegalStateException", "Image was released");
     }
 
-    readerFormat = Image_getPixelFormat(env, readerFormat);
+    int halReaderFormat = android_view_Surface_mapPublicFormatToHalFormat(
+        publicReaderFormat);
 
-    rowStride = Image_imageGetRowStride(env, buffer, idx, readerFormat);
-    pixelStride = Image_imageGetPixelStride(env, buffer, idx, readerFormat);
+    rowStride = Image_imageGetRowStride(env, buffer, idx, halReaderFormat);
+    pixelStride = Image_imageGetPixelStride(env, buffer, idx, halReaderFormat);
 
     jobject surfPlaneObj = env->NewObject(gSurfacePlaneClassInfo.clazz,
             gSurfacePlaneClassInfo.ctor, thiz, idx, rowStride, pixelStride);
@@ -909,6 +905,7 @@
     uint8_t *base = NULL;
     uint32_t size = 0;
     jobject byteBuffer;
+    PublicFormat readerPublicFormat = static_cast<PublicFormat>(readerFormat);
 
     ALOGV("%s: buffer index: %d", __FUNCTION__, idx);
 
@@ -918,10 +915,11 @@
         jniThrowException(env, "java/lang/IllegalStateException", "Image was released");
     }
 
-    readerFormat = Image_getPixelFormat(env, readerFormat);
+    int readerHalFormat = android_view_Surface_mapPublicFormatToHalFormat(
+            readerPublicFormat);
 
     // Create byteBuffer from native buffer
-    Image_getLockedBufferInfo(env, buffer, idx, &base, &size, readerFormat);
+    Image_getLockedBufferInfo(env, buffer, idx, &base, &size, readerHalFormat);
 
     if (size > static_cast<uint32_t>(INT32_MAX)) {
         // Byte buffer have 'int capacity', so check the range
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index dfe2844..1205f9d 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -256,7 +256,7 @@
     dump();
 
     // allocate a channel
-    channel = allocateChannel_l(priority, sampleID);
+    channel = allocateChannel_l(priority);
 
     // no channel allocated - return 0
     if (!channel) {
@@ -271,25 +271,13 @@
     return channelID;
 }
 
-SoundChannel* SoundPool::allocateChannel_l(int priority, int sampleID)
+SoundChannel* SoundPool::allocateChannel_l(int priority)
 {
     List<SoundChannel*>::iterator iter;
     SoundChannel* channel = NULL;
 
-    // check if channel for given sampleID still available
+    // allocate a channel
     if (!mChannels.empty()) {
-        for (iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
-            if (sampleID == (*iter)->getPrevSampleID() && (*iter)->state() == SoundChannel::IDLE) {
-                channel = *iter;
-                mChannels.erase(iter);
-                ALOGV("Allocated recycled channel for same sampleID");
-                break;
-            }
-        }
-    }
-
-    // allocate any channel
-    if (!channel && !mChannels.empty()) {
         iter = mChannels.begin();
         if (priority >= (*iter)->priority()) {
             channel = *iter;
@@ -638,7 +626,7 @@
        goto error;
     }
 
-    if ((numChannels < 1) || (numChannels > 2)) {
+    if ((numChannels < 1) || (numChannels > 8)) {
         ALOGE("Sample channel count (%d) out of range", numChannels);
         status = BAD_VALUE;
         goto error;
@@ -660,7 +648,6 @@
 void SoundChannel::init(SoundPool* soundPool)
 {
     mSoundPool = soundPool;
-    mPrevSampleID = -1;
 }
 
 // call with sound pool lock held
@@ -669,7 +656,7 @@
 {
     sp<AudioTrack> oldTrack;
     sp<AudioTrack> newTrack;
-    status_t status = NO_ERROR;
+    status_t status;
 
     { // scope for the lock
         Mutex::Autolock lock(&mLock);
@@ -702,8 +689,10 @@
         size_t frameCount = 0;
 
         if (loop) {
-            frameCount = sample->size()/numChannels/
-                ((sample->format() == AUDIO_FORMAT_PCM_16_BIT) ? sizeof(int16_t) : sizeof(uint8_t));
+            const audio_format_t format = sample->format();
+            const size_t frameSize = audio_is_linear_pcm(format)
+                    ? numChannels * audio_bytes_per_sample(format) : 1;
+            frameCount = sample->size() / frameSize;
         }
 
 #ifndef USE_SHARED_MEM_BUFFER
@@ -714,43 +703,38 @@
         }
 #endif
 
-        if (!mAudioTrack.get() || mPrevSampleID != sample->sampleID()) {
-            // mToggle toggles each time a track is started on a given channel.
-            // The toggle is concatenated with the SoundChannel address and passed to AudioTrack
-            // as callback user data. This enables the detection of callbacks received from the old
-            // audio track while the new one is being started and avoids processing them with
-            // wrong audio audio buffer size  (mAudioBufferSize)
-            unsigned long toggle = mToggle ^ 1;
-            void *userData = (void *)((unsigned long)this | toggle);
-            audio_channel_mask_t channelMask = audio_channel_out_mask_from_count(numChannels);
+        // mToggle toggles each time a track is started on a given channel.
+        // The toggle is concatenated with the SoundChannel address and passed to AudioTrack
+        // as callback user data. This enables the detection of callbacks received from the old
+        // audio track while the new one is being started and avoids processing them with
+        // wrong audio audio buffer size  (mAudioBufferSize)
+        unsigned long toggle = mToggle ^ 1;
+        void *userData = (void *)((unsigned long)this | toggle);
+        audio_channel_mask_t channelMask = audio_channel_out_mask_from_count(numChannels);
 
-            // do not create a new audio track if current track is compatible with sample parameters
-    #ifdef USE_SHARED_MEM_BUFFER
-            newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
-                    channelMask, sample->getIMemory(), AUDIO_OUTPUT_FLAG_FAST, callback, userData);
-    #else
-            uint32_t bufferFrames = (totalFrames + (kDefaultBufferCount - 1)) / kDefaultBufferCount;
-            newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
-                    channelMask, frameCount, AUDIO_OUTPUT_FLAG_FAST, callback, userData,
-                    bufferFrames);
-    #endif
-            oldTrack = mAudioTrack;
-            status = newTrack->initCheck();
-            if (status != NO_ERROR) {
-                ALOGE("Error creating AudioTrack");
-                goto exit;
-            }
-            // From now on, AudioTrack callbacks received with previous toggle value will be ignored.
-            mToggle = toggle;
-            mAudioTrack = newTrack;
-            ALOGV("using new track %p for sample %d", newTrack.get(), sample->sampleID());
-        } else {
-            newTrack = mAudioTrack;
-            newTrack->setSampleRate(sampleRate);
-            ALOGV("reusing track %p for sample %d", mAudioTrack.get(), sample->sampleID());
+        // do not create a new audio track if current track is compatible with sample parameters
+#ifdef USE_SHARED_MEM_BUFFER
+        newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
+                channelMask, sample->getIMemory(), AUDIO_OUTPUT_FLAG_FAST, callback, userData);
+#else
+        uint32_t bufferFrames = (totalFrames + (kDefaultBufferCount - 1)) / kDefaultBufferCount;
+        newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
+                channelMask, frameCount, AUDIO_OUTPUT_FLAG_FAST, callback, userData,
+                bufferFrames);
+#endif
+        oldTrack = mAudioTrack;
+        status = newTrack->initCheck();
+        if (status != NO_ERROR) {
+            ALOGE("Error creating AudioTrack");
+            goto exit;
         }
+        ALOGV("setVolume %p", newTrack.get());
         newTrack->setVolume(leftVolume, rightVolume);
         newTrack->setLoop(0, frameCount, loop);
+
+        // From now on, AudioTrack callbacks received with previous toggle value will be ignored.
+        mToggle = toggle;
+        mAudioTrack = newTrack;
         mPos = 0;
         mSample = sample;
         mChannelID = nextChannelID;
@@ -893,7 +877,6 @@
         setVolume_l(0, 0);
         ALOGV("stop");
         mAudioTrack->stop();
-        mPrevSampleID = mSample->sampleID();
         mSample.clear();
         mState = IDLE;
         mPriority = IDLE_PRIORITY;
diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h
index f520406..d19cd91 100644
--- a/media/jni/soundpool/SoundPool.h
+++ b/media/jni/soundpool/SoundPool.h
@@ -72,8 +72,8 @@
     volatile int32_t    mRefCount;
     uint16_t            mSampleID;
     uint16_t            mSampleRate;
-    uint8_t             mState : 3;
-    uint8_t             mNumChannels : 2;
+    uint8_t             mState;
+    uint8_t             mNumChannels;
     audio_format_t      mFormat;
     int                 mFd;
     int64_t             mOffset;
@@ -136,7 +136,6 @@
     void nextEvent();
     int nextChannelID() { return mNextEvent.channelID(); }
     void dump();
-    int getPrevSampleID(void) { return mPrevSampleID; }
 
 private:
     static void callback(int event, void* user, void *info);
@@ -153,7 +152,6 @@
     int                 mAudioBufferSize;
     unsigned long       mToggle;
     bool                mAutoPaused;
-    int                 mPrevSampleID;
 };
 
 // application object for managing a pool of sounds
@@ -195,7 +193,7 @@
     sp<Sample> findSample(int sampleID) { return mSamples.valueFor(sampleID); }
     SoundChannel* findChannel (int channelID);
     SoundChannel* findNextChannel (int channelID);
-    SoundChannel* allocateChannel_l(int priority, int sampleID);
+    SoundChannel* allocateChannel_l(int priority);
     void moveToFront_l(SoundChannel* channel);
     void notify(SoundPoolEvent event);
     void dump();
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 3cae19d..d756d05 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -161,8 +161,7 @@
         assertFalse(request.isEmpty());
         assertFalse(metadata.isEmpty());
         if (needStream) {
-            int streamId = mCameraUser.createStream(/* ignored */10, /* ignored */20,
-                    /* ignored */30, mSurface);
+            int streamId = mCameraUser.createStream(mSurface);
             assertEquals(0, streamId);
             request.addTarget(mSurface);
         }
@@ -235,12 +234,11 @@
 
     @SmallTest
     public void testCreateStream() throws Exception {
-        int streamId = mCameraUser.createStream(/* ignored */10, /* ignored */20, /* ignored */30,
-                mSurface);
+        int streamId = mCameraUser.createStream(mSurface);
         assertEquals(0, streamId);
 
         assertEquals(CameraBinderTestUtils.ALREADY_EXISTS,
-                mCameraUser.createStream(/* ignored */0, /* ignored */0, /* ignored */0, mSurface));
+                mCameraUser.createStream(mSurface));
 
         assertEquals(CameraBinderTestUtils.NO_ERROR, mCameraUser.deleteStream(streamId));
     }
@@ -257,20 +255,18 @@
     public void testCreateStreamTwo() throws Exception {
 
         // Create first stream
-        int streamId = mCameraUser.createStream(/* ignored */0, /* ignored */0, /* ignored */0,
-                mSurface);
+        int streamId = mCameraUser.createStream(mSurface);
         assertEquals(0, streamId);
 
         assertEquals(CameraBinderTestUtils.ALREADY_EXISTS,
-                mCameraUser.createStream(/* ignored */0, /* ignored */0, /* ignored */0, mSurface));
+                mCameraUser.createStream(mSurface));
 
         // Create second stream with a different surface.
         SurfaceTexture surfaceTexture = new SurfaceTexture(/* ignored */0);
         surfaceTexture.setDefaultBufferSize(640, 480);
         Surface surface2 = new Surface(surfaceTexture);
 
-        int streamId2 = mCameraUser.createStream(/* ignored */0, /* ignored */0, /* ignored */0,
-                surface2);
+        int streamId2 = mCameraUser.createStream(surface2);
         assertEquals(1, streamId2);
 
         // Clean up streams
diff --git a/packages/SystemUI/res/layout/remote_input.xml b/packages/SystemUI/res/layout/remote_input.xml
new file mode 100644
index 0000000..8ca5634
--- /dev/null
+++ b/packages/SystemUI/res/layout/remote_input.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2015 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
+  -->
+
+<!-- FrameLayout -->
+<com.android.systemui.statusbar.policy.RemoteInputView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:theme="@style/systemui_theme_light"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:paddingStart="4dp"
+        android:paddingEnd="2dp"
+        android:paddingBottom="4dp"
+        android:paddingTop="2dp">
+
+    <view class="com.android.systemui.statusbar.policy.RemoteInputView$RemoteEditText"
+            android:id="@+id/remote_input_text"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:singleLine="true"
+            android:imeOptions="actionSend" />
+
+    <ProgressBar
+            android:id="@+id/remote_input_progress"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom"
+            android:visibility="invisible"
+            android:indeterminate="true"
+            style="?android:attr/progressBarStyleHorizontal" />
+
+</com.android.systemui.statusbar.policy.RemoteInputView>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 94f77c6..07fcb82 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -211,6 +211,11 @@
         <item name="android:colorControlActivated">@color/system_accent_color</item>
     </style>
 
+    <style name="systemui_theme_light" parent="@android:style/Theme.DeviceDefault.Light">
+        <item name="android:colorPrimary">@color/system_primary_color</item>
+        <item name="android:colorControlActivated">@color/system_accent_color</item>
+    </style>
+
     <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog">
         <item name="android:colorPrimary">@color/system_primary_color</item>
         <item name="android:colorControlActivated">@color/system_accent_color</item>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 37d9a73..8347a22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -24,6 +24,7 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.RemoteInput;
 import android.app.TaskStackBuilder;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
@@ -49,6 +50,7 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -98,6 +100,7 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.HeadsUpNotificationView;
 import com.android.systemui.statusbar.policy.PreviewInflater;
+import com.android.systemui.statusbar.policy.RemoteInputView;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
 import java.util.ArrayList;
@@ -117,6 +120,9 @@
     // STOPSHIP disable once we resolve b/18102199
     private static final boolean NOTIFICATION_CLICK_DEBUG = true;
 
+    public static final boolean ENABLE_REMOTE_INPUT =
+            Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.enable_remote_input", false);
+
     protected static final int MSG_SHOW_RECENT_APPS = 1019;
     protected static final int MSG_HIDE_RECENT_APPS = 1020;
     protected static final int MSG_TOGGLE_RECENTS_APPS = 1021;
@@ -410,6 +416,7 @@
                 @Override
                 public void run() {
                     for (StatusBarNotification sbn : notifications) {
+                        processForRemoteInput(sbn.getNotification());
                         addNotification(sbn, currentRanking);
                     }
                 }
@@ -424,6 +431,7 @@
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
+                        processForRemoteInput(sbn.getNotification());
                         Notification n = sbn.getNotification();
                         boolean isUpdate = mNotificationData.get(sbn.getKey()) != null
                                 || isHeadsUp(sbn.getKey());
@@ -1357,6 +1365,9 @@
                 (NotificationContentView) row.findViewById(R.id.expandedPublic);
 
         row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+        if (ENABLE_REMOTE_INPUT) {
+            row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+        }
 
         PendingIntent contentIntent = sbn.getNotification().contentIntent;
         if (contentIntent != null) {
@@ -1518,9 +1529,103 @@
         }
         row.setUserLocked(userLocked);
         row.setStatusBarNotification(entry.notification);
+        applyRemoteInput(entry);
         return true;
     }
 
+    /**
+     * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this
+     * via first-class API.
+     *
+     * TODO: Remove once enough apps specify remote inputs on their own.
+     */
+    private void processForRemoteInput(Notification n) {
+        if (!ENABLE_REMOTE_INPUT) return;
+
+        if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") &&
+                (n.actions == null || n.actions.length == 0)) {
+            Notification.Action viableAction = null;
+            Notification.WearableExtender we = new Notification.WearableExtender(n);
+
+            List<Notification.Action> actions = we.getActions();
+            final int numActions = actions.size();
+
+            for (int i = 0; i < numActions; i++) {
+                Notification.Action action = actions.get(i);
+                RemoteInput[] remoteInputs = action.getRemoteInputs();
+                for (RemoteInput ri : action.getRemoteInputs()) {
+                    if (ri.getAllowFreeFormInput()) {
+                        viableAction = action;
+                        break;
+                    }
+                }
+                if (viableAction != null) {
+                    break;
+                }
+            }
+
+            if (viableAction != null) {
+                Notification stripped = n.clone();
+                Notification.Builder.stripForDelivery(stripped);
+                stripped.actions = new Notification.Action[] { viableAction };
+                stripped.extras.putBoolean("android.rebuild.contentView", true);
+                stripped.contentView = null;
+                stripped.extras.putBoolean("android.rebuild.bigView", true);
+                stripped.bigContentView = null;
+
+                // Don't create the HUN input view for now because input doesn't work there yet.
+                // TODO: Enable once HUNs can take remote input correctly.
+                if (false) {
+                    stripped.extras.putBoolean("android.rebuild.hudView", true);
+                    stripped.headsUpContentView = null;
+                }
+
+                Notification rebuilt = Notification.Builder.rebuild(mContext, stripped);
+
+                n.actions = rebuilt.actions;
+                n.bigContentView = rebuilt.bigContentView;
+                n.headsUpContentView = rebuilt.headsUpContentView;
+                n.publicVersion = rebuilt.publicVersion;
+            }
+        }
+    }
+
+    private void applyRemoteInput(final Entry entry) {
+        if (!ENABLE_REMOTE_INPUT) return;
+
+        RemoteInput remoteInput = null;
+
+        // See if the notification has exactly one action and this action allows free-form input
+        // TODO: relax restrictions once we support more than one remote input action.
+        Notification.Action[] actions = entry.notification.getNotification().actions;
+        if (actions != null && actions.length == 1) {
+            if (actions[0].getRemoteInputs() != null) {
+                for (RemoteInput ri : actions[0].getRemoteInputs()) {
+                    if (ri.getAllowFreeFormInput()) {
+                        remoteInput = ri;
+                        break;
+                    }
+                }
+            }
+        }
+
+        // See if we have somewhere to put that remote input
+        ViewGroup actionContainer = null;
+        if (remoteInput != null && entry.expandedBig != null) {
+            View actionContainerCandidate = entry.expandedBig
+                    .findViewById(com.android.internal.R.id.actions);
+            if (actionContainerCandidate instanceof ViewGroup) {
+                actionContainer = (ViewGroup) actionContainerCandidate;
+            }
+        }
+
+        if (actionContainer != null) {
+            actionContainer.removeAllViews();
+            actionContainer.addView(
+                    RemoteInputView.inflate(mContext, actionContainer, actions[0], remoteInput));
+        }
+    }
+
     public NotificationClicker makeClicker(PendingIntent intent, String notificationKey) {
         return new NotificationClicker(intent, notificationKey);
     }
@@ -2037,6 +2142,8 @@
         entry.row.setStatusBarNotification(notification);
         entry.row.notifyContentUpdated();
         entry.row.resetHeight();
+
+        applyRemoteInput(entry);
     }
 
     protected void notifyHeadsUpScreenOn(boolean screenOn) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index eba7d9f..63bbf97 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -115,7 +115,8 @@
 
     private void applyFocusableFlag(State state) {
         if (state.isKeyguardShowingAndNotOccluded() && state.keyguardNeedsInput
-                && state.bouncerShowing) {
+                && state.bouncerShowing
+                || BaseStatusBar.ENABLE_REMOTE_INPUT && state.statusBarExpanded) {
             mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
             mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
         } else if (state.isKeyguardShowingAndNotOccluded() || state.statusBarFocusable) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
new file mode 100644
index 0000000..7d721c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2015 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.systemui.statusbar.policy;
+
+import com.android.systemui.R;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+/**
+ * Host for the remote input.
+ */
+public class RemoteInputView extends FrameLayout implements View.OnClickListener {
+
+    private static final String TAG = "RemoteInput";
+
+    private RemoteEditText mEditText;
+    private ProgressBar mProgressBar;
+    private PendingIntent mPendingIntent;
+    private RemoteInput mRemoteInput;
+    private Notification.Action mAction;
+
+    public RemoteInputView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mProgressBar = (ProgressBar) findViewById(R.id.remote_input_progress);
+
+        mEditText = (RemoteEditText) getChildAt(0);
+        mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+
+                // Check if this was the result of hitting the enter key
+                final boolean isSoftImeEvent = event == null
+                        && (actionId == EditorInfo.IME_ACTION_DONE
+                        || actionId == EditorInfo.IME_ACTION_NEXT
+                        || actionId == EditorInfo.IME_ACTION_SEND);
+                final boolean isKeyboardEnterKey = event != null
+                        && KeyEvent.isConfirmKey(event.getKeyCode())
+                        && event.getAction() == KeyEvent.ACTION_DOWN;
+
+                if (isSoftImeEvent || isKeyboardEnterKey) {
+                    sendRemoteInput();
+                    return true;
+                }
+                return false;
+            }
+        });
+        mEditText.setOnClickListener(this);
+        mEditText.setInnerFocusable(false);
+    }
+
+    private void sendRemoteInput() {
+        Bundle results = new Bundle();
+        results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
+        Intent fillInIntent = new Intent();
+        RemoteInput.addResultsToIntent(mAction.getRemoteInputs(), fillInIntent,
+                results);
+
+        mEditText.setEnabled(false);
+        mProgressBar.setVisibility(VISIBLE);
+
+        try {
+            mPendingIntent.send(mContext, 0, fillInIntent);
+        } catch (PendingIntent.CanceledException e) {
+            Log.i(TAG, "Unable to send remote input result", e);
+        }
+    }
+
+    public static RemoteInputView inflate(Context context, ViewGroup root,
+            Notification.Action action, RemoteInput remoteInput) {
+        RemoteInputView v = (RemoteInputView)
+                LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
+
+        v.mEditText.setHint(action.title);
+        v.mPendingIntent = action.actionIntent;
+        v.mRemoteInput = remoteInput;
+        v.mAction = action;
+
+        return v;
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v == mEditText) {
+            if (!mEditText.isFocusable()) {
+                mEditText.setInnerFocusable(true);
+                InputMethodManager imm = InputMethodManager.getInstance();
+                if (imm != null) {
+                    imm.viewClicked(mEditText);
+                    imm.showSoftInput(mEditText, 0);
+                }
+            }
+        }
+    }
+
+    /**
+     * An EditText that changes appearance based on whether it's focusable and becomes
+     * un-focusable whenever the user navigates away from it or it becomes invisible.
+     */
+    public static class RemoteEditText extends EditText {
+
+        private final Drawable mBackground;
+
+        public RemoteEditText(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            mBackground = getBackground();
+        }
+
+        private void defocusIfNeeded() {
+            if (isFocusable() && isEnabled()) {
+                setInnerFocusable(false);
+            }
+        }
+
+        @Override
+        protected void onVisibilityChanged(View changedView, int visibility) {
+            super.onVisibilityChanged(changedView, visibility);
+
+            if (!isShown()) {
+                defocusIfNeeded();
+            }
+        }
+
+        @Override
+        protected void onFocusLost() {
+            super.onFocusLost();
+            defocusIfNeeded();
+        }
+
+        @Override
+        public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                defocusIfNeeded();
+            }
+            return super.onKeyPreIme(keyCode, event);
+        }
+
+
+        void setInnerFocusable(boolean focusable) {
+            setFocusableInTouchMode(focusable);
+            setFocusable(focusable);
+            setCursorVisible(focusable);
+
+            if (focusable) {
+                requestFocus();
+                setBackground(mBackground);
+            } else {
+                setBackground(null);
+            }
+
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d8a9d3e..46f07cc 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2401,7 +2401,7 @@
                                     }
                                     ps.addCpuTimeLocked(st.rel_utime - otherUTime,
                                             st.rel_stime - otherSTime, cpuSpeedTimes);
-                                    pr.curCpuTime += (st.rel_utime+st.rel_stime) * 10;
+                                    pr.curCpuTime += st.rel_utime + st.rel_stime;
                                 } else {
                                     BatteryStatsImpl.Uid.Proc ps = st.batteryStats;
                                     if (ps == null || !ps.isActive()) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 7a29a88..8fe1238 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -305,17 +305,16 @@
                     r.resultExtras, r.resultAbort, false);
             reschedule = true;
         }
-
-        r = mPendingBroadcast;
-        if (r != null && r.curApp == app) {
+        if (r == null && mPendingBroadcast != null && mPendingBroadcast.curApp == app) {
             if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
                     "[" + mQueueName + "] skip & discard pending app " + r);
+            r = mPendingBroadcast;
+        }
+
+        if (r != null) {
             logBroadcastReceiverDiscardLocked(r);
             finishReceiverLocked(r, r.resultCode, r.resultData,
                     r.resultExtras, r.resultAbort, false);
-            reschedule = true;
-        }
-        if (reschedule) {
             scheduleBroadcastsLocked();
         }
     }
@@ -512,10 +511,11 @@
                 }
             }
             try {
-                if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST, "Delivering to " + filter + " : " + r);
+                if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,
+                        "Delivering to " + filter + " : " + r);
                 performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
-                    new Intent(r.intent), r.resultCode, r.resultData,
-                    r.resultExtras, r.ordered, r.initialSticky, r.userId);
+                        new Intent(r.intent), r.resultCode, r.resultData,
+                        r.resultExtras, r.ordered, r.initialSticky, r.userId);
                 if (ordered) {
                     r.state = BroadcastRecord.CALL_DONE_RECEIVE;
                 }
@@ -574,11 +574,9 @@
             // broadcast, then do nothing at this point.  Just in case, we
             // check that the process we're waiting for still exists.
             if (mPendingBroadcast != null) {
-                if (DEBUG_BROADCAST_LIGHT) {
-                    Slog.v(TAG_BROADCAST, "processNextBroadcast ["
-                            + mQueueName + "]: waiting for "
-                            + mPendingBroadcast.curApp);
-                }
+                if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
+                        "processNextBroadcast [" + mQueueName + "]: waiting for "
+                        + mPendingBroadcast.curApp);
 
                 boolean isDead;
                 synchronized (mService.mPidsSelfLocked) {
@@ -677,8 +675,8 @@
                     if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Cancelling BROADCAST_TIMEOUT_MSG");
                     cancelBroadcastTimeoutLocked();
 
-                    if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Finished with ordered broadcast "
-                            + r);
+                    if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
+                            "Finished with ordered broadcast " + r);
 
                     // ... and on to the next...
                     addBroadcastToHistoryLocked(r);
@@ -834,19 +832,18 @@
                             + info.activityInfo.packageName, e);
                 }
                 if (!isAvailable) {
-                    if (DEBUG_BROADCAST) {
-                        Slog.v(TAG_BROADCAST, "Skipping delivery to " + info.activityInfo.packageName
-                                + " / " + info.activityInfo.applicationInfo.uid
-                                + " : package no longer available");
-                    }
+                    if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
+                            "Skipping delivery to " + info.activityInfo.packageName + " / "
+                            + info.activityInfo.applicationInfo.uid
+                            + " : package no longer available");
                     skip = true;
                 }
             }
 
             if (skip) {
                 if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                        "Skipping delivery of ordered ["
-                        + mQueueName + "] " + r + " for whatever reason");
+                        "Skipping delivery of ordered [" + mQueueName + "] "
+                        + r + " for whatever reason");
                 r.receiver = null;
                 r.curFilter = null;
                 r.state = BroadcastRecord.IDLE;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5d386bd..61a7263 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3620,7 +3620,9 @@
             pw.print("   Devices: ");
             final int devices = AudioSystem.getDevicesForStream(mStreamType);
             int device, i = 0, n = 0;
-            while ((device = 1 << i) <= AudioSystem.DEVICE_OUT_DEFAULT) {
+            // iterate all devices from 1 to DEVICE_OUT_DEFAULT exclusive
+            // (the default device is not returned by getDevicesForStream)
+            while ((device = 1 << i) != AudioSystem.DEVICE_OUT_DEFAULT) {
                 if ((devices & device) != 0) {
                     if (n++ > 0) {
                         pw.print(", ");
diff --git a/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java b/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java
index 533b8bc..6263463 100644
--- a/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java
+++ b/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java
@@ -21,37 +21,37 @@
 
 /**
  * Record of energy and activity information from controller and
- * underlying wifi stack state.Timestamp the record with system
- * time
+ * underlying wifi stack state. Timestamp the record with elapsed
+ * real-time.
  * @hide
  */
 public final class WifiActivityEnergyInfo implements Parcelable {
+    private final long mTimestamp;
     private final int mStackState;
     private final int mControllerTxTimeMs;
     private final int mControllerRxTimeMs;
     private final int mControllerIdleTimeMs;
     private final int mControllerEnergyUsed;
-    private final long timestamp;
 
     public static final int STACK_STATE_INVALID = 0;
     public static final int STACK_STATE_STATE_ACTIVE = 1;
     public static final int STACK_STATE_STATE_SCANNING = 2;
     public static final int STACK_STATE_STATE_IDLE = 3;
 
-    public WifiActivityEnergyInfo(int stackState, int txTime, int rxTime,
-                                  int idleTime, int energyUsed) {
+    public WifiActivityEnergyInfo(long timestamp, int stackState,
+                                  int txTime, int rxTime, int idleTime, int energyUsed) {
+        mTimestamp = timestamp;
         mStackState = stackState;
         mControllerTxTimeMs = txTime;
         mControllerRxTimeMs = rxTime;
         mControllerIdleTimeMs = idleTime;
         mControllerEnergyUsed = energyUsed;
-        timestamp = System.currentTimeMillis();
     }
 
     @Override
     public String toString() {
         return "WifiActivityEnergyInfo{"
-            + " timestamp=" + timestamp
+            + " timestamp=" + mTimestamp
             + " mStackState=" + mStackState
             + " mControllerTxTimeMs=" + mControllerTxTimeMs
             + " mControllerRxTimeMs=" + mControllerRxTimeMs
@@ -63,13 +63,14 @@
     public static final Parcelable.Creator<WifiActivityEnergyInfo> CREATOR =
             new Parcelable.Creator<WifiActivityEnergyInfo>() {
         public WifiActivityEnergyInfo createFromParcel(Parcel in) {
+            long timestamp = in.readLong();
             int stackState = in.readInt();
             int txTime = in.readInt();
             int rxTime = in.readInt();
             int idleTime = in.readInt();
             int energyUsed = in.readInt();
-            return new WifiActivityEnergyInfo(stackState, txTime, rxTime,
-                    idleTime, energyUsed);
+            return new WifiActivityEnergyInfo(timestamp, stackState,
+                    txTime, rxTime, idleTime, energyUsed);
         }
         public WifiActivityEnergyInfo[] newArray(int size) {
             return new WifiActivityEnergyInfo[size];
@@ -77,6 +78,7 @@
     };
 
     public void writeToParcel(Parcel out, int flags) {
+        out.writeLong(mTimestamp);
         out.writeInt(mStackState);
         out.writeInt(mControllerTxTimeMs);
         out.writeInt(mControllerRxTimeMs);
@@ -127,7 +129,7 @@
      * @return timestamp(wall clock) of record creation
      */
     public long getTimeStamp() {
-        return timestamp;
+        return mTimestamp;
     }
 
     /**