Merge "Cache photo URI too in calllog (contract)"
diff --git a/api/current.txt b/api/current.txt
index cdbf2ef1..489fd05 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 d080a63..7fa8d9b 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/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4a3650f..e23ffe4 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3681,9 +3681,14 @@
     /**
      * Check whether the current user has been blocked by device policy from uninstalling a package.
      * Requires the caller to be the profile owner if checking a specific admin's policy.
+     * <p>
+     * <strong>Note:</strong> Starting from {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}, the
+     * behavior of this API is changed such that passing <code>null</code> as the <code>admin</code>
+     * parameter will return if any admin has blocked the uninstallation. Before L MR1, passing
+     * <code>null</code> will cause a NullPointerException to be raised.
      *
      * @param admin The name of the admin component whose blocking policy will be checked, or null
-     *        to check if any admin has blocked the uninstallation.
+     *            to check if any admin has blocked the uninstallation.
      * @param packageName package to check.
      * @return true if uninstallation is blocked.
      */
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/GLES20Canvas.java b/core/java/android/view/DisplayListCanvas.java
similarity index 85%
rename from core/java/android/view/GLES20Canvas.java
rename to core/java/android/view/DisplayListCanvas.java
index 06e196d..90e1f86 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/DisplayListCanvas.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.NonNull;
 import android.graphics.Bitmap;
 import android.graphics.CanvasProperty;
 import android.graphics.NinePatch;
@@ -24,19 +25,50 @@
 import android.graphics.Picture;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.util.Pools.SynchronizedPool;
 
 /**
- * An implementation of Canvas on top of OpenGL ES 2.0.
+ * An implementation of a GL canvas that records drawing operations.
+ * This is intended for use with a DisplayList. This class keeps a list of all the Paint and
+ * Bitmap objects that it draws, preventing the backing memory of Bitmaps from being freed while
+ * the DisplayList is still holding a native reference to the memory.
  */
-class GLES20Canvas extends HardwareCanvas {
+class DisplayListCanvas extends HardwareCanvas {
+    // The recording canvas pool should be large enough to handle a deeply nested
+    // view hierarchy because display lists are generated recursively.
+    private static final int POOL_LIMIT = 25;
+
+    private static final SynchronizedPool<DisplayListCanvas> sPool =
+            new SynchronizedPool<DisplayListCanvas>(POOL_LIMIT);
+
+    RenderNode mNode;
     private int mWidth;
     private int mHeight;
 
-    private float[] mPoint;
-    private float[] mLine;
 
-    private Rect mClipBounds;
-    private RectF mPathBounds;
+    static DisplayListCanvas obtain(@NonNull RenderNode node) {
+        if (node == null) throw new IllegalArgumentException("node cannot be null");
+        DisplayListCanvas canvas = sPool.acquire();
+        if (canvas == null) {
+            canvas = new DisplayListCanvas();
+        }
+        canvas.mNode = node;
+        return canvas;
+    }
+
+    void recycle() {
+        mNode = null;
+        sPool.release(this);
+    }
+
+    long finishRecording() {
+        return nFinishRecording(mNativeCanvasWrapper);
+    }
+
+    @Override
+    public boolean isRecordingFor(Object o) {
+        return o == mNode;
+    }
 
     ///////////////////////////////////////////////////////////////////////////
     // JNI
@@ -53,8 +85,8 @@
     // Constructors
     ///////////////////////////////////////////////////////////////////////////
 
-    // TODO: Merge with GLES20RecordingCanvas
-    protected GLES20Canvas() {
+
+    private DisplayListCanvas() {
         super(nCreateDisplayListRenderer());
     }
 
diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java
deleted file mode 100644
index 6c780c9..0000000
--- a/core/java/android/view/GLES20RecordingCanvas.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2010 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.view;
-
-import android.annotation.NonNull;
-import android.util.Pools.SynchronizedPool;
-
-/**
- * An implementation of a GL canvas that records drawing operations.
- * This is intended for use with a DisplayList. This class keeps a list of all the Paint and
- * Bitmap objects that it draws, preventing the backing memory of Bitmaps from being freed while
- * the DisplayList is still holding a native reference to the memory.
- */
-class GLES20RecordingCanvas extends GLES20Canvas {
-    // The recording canvas pool should be large enough to handle a deeply nested
-    // view hierarchy because display lists are generated recursively.
-    private static final int POOL_LIMIT = 25;
-
-    private static final SynchronizedPool<GLES20RecordingCanvas> sPool =
-            new SynchronizedPool<GLES20RecordingCanvas>(POOL_LIMIT);
-
-    RenderNode mNode;
-
-    private GLES20RecordingCanvas() {
-        super();
-    }
-
-    static GLES20RecordingCanvas obtain(@NonNull RenderNode node) {
-        if (node == null) throw new IllegalArgumentException("node cannot be null");
-        GLES20RecordingCanvas canvas = sPool.acquire();
-        if (canvas == null) {
-            canvas = new GLES20RecordingCanvas();
-        }
-        canvas.mNode = node;
-        return canvas;
-    }
-
-    void recycle() {
-        mNode = null;
-        sPool.release(this);
-    }
-
-    long finishRecording() {
-        return nFinishRecording(mNativeCanvasWrapper);
-    }
-
-    @Override
-    public boolean isRecordingFor(Object o) {
-        return o == mNode;
-    }
-}
diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
index cdb350f..fc2b55b 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -119,6 +119,6 @@
             CanvasProperty<Paint> paint);
 
     public static void setProperty(String name, String value) {
-        GLES20Canvas.setProperty(name, value);
+        DisplayListCanvas.setProperty(name, value);
     }
 }
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 9921be2..afa7f51 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -205,7 +205,7 @@
      *         false otherwise
      */
     public static boolean isAvailable() {
-        return GLES20Canvas.isAvailable();
+        return DisplayListCanvas.isAvailable();
     }
 
     /**
@@ -423,7 +423,7 @@
      */
     static HardwareRenderer create(Context context, boolean translucent) {
         HardwareRenderer renderer = null;
-        if (GLES20Canvas.isAvailable()) {
+        if (DisplayListCanvas.isAvailable()) {
             renderer = new ThreadedRenderer(context, translucent);
         }
         return renderer;
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/view/RenderNode.java b/core/java/android/view/RenderNode.java
index 09eb486..38867a8 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -225,7 +225,7 @@
      * @see #isValid()
      */
     public HardwareCanvas start(int width, int height) {
-        HardwareCanvas canvas = GLES20RecordingCanvas.obtain(this);
+        HardwareCanvas canvas = DisplayListCanvas.obtain(this);
         canvas.setViewport(width, height);
         // The dirty rect should always be null for a display list
         canvas.onPreDraw(null);
@@ -241,11 +241,11 @@
      * @see #isValid()
      */
     public void end(HardwareCanvas endCanvas) {
-        if (!(endCanvas instanceof GLES20RecordingCanvas)) {
+        if (!(endCanvas instanceof DisplayListCanvas)) {
             throw new IllegalArgumentException("Passed an invalid canvas to end!");
         }
 
-        GLES20RecordingCanvas canvas = (GLES20RecordingCanvas) endCanvas;
+        DisplayListCanvas canvas = (DisplayListCanvas) endCanvas;
         canvas.onPostDraw();
         long renderNodeData = canvas.finishRecording();
         nSetDisplayListData(mNativeRenderNode, renderNodeData);
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index 7b35a3b..379796d 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -283,10 +283,10 @@
     }
 
     public void setTarget(Canvas canvas) {
-        if (!(canvas instanceof GLES20RecordingCanvas)) {
+        if (!(canvas instanceof DisplayListCanvas)) {
             throw new IllegalArgumentException("Not a GLES20RecordingCanvas");
         }
-        final GLES20RecordingCanvas recordingCanvas = (GLES20RecordingCanvas) canvas;
+        final DisplayListCanvas recordingCanvas = (DisplayListCanvas) canvas;
         setTarget(recordingCanvas.mNode);
     }
 
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 8197b3d..cc44577 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;
@@ -4885,9 +4887,10 @@
                     text.insert(newTextInsertAt, newText);
                 }
             }
-            // Restore the cursor position.
+            // Restore the cursor position. If there wasn't an old cursor (newCursorPos == -1) then
+            // don't explicitly set it and rely on SpannableStringBuilder to position it.
             // TODO: Select all the text that was undone.
-            if (newCursorPos <= text.length()) {
+            if (0 <= newCursorPos && newCursorPos <= text.length()) {
                 Selection.setSelection(text, newCursorPos);
             }
         }
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index a224f5e..d12739f 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -515,6 +515,23 @@
             }
         }
 
+        // Use the top-start-most laid out view as the baseline. RTL offsets are
+        // applied later, so we can use the left-most edge as the starting edge.
+        View baselineView = null;
+        LayoutParams baselineParams = null;
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                final LayoutParams childParams = (LayoutParams) child.getLayoutParams();
+                if (baselineView == null || baselineParams == null
+                        || compareLayoutPosition(childParams, baselineParams) < 0) {
+                    baselineView = child;
+                    baselineParams = childParams;
+                }
+            }
+        }
+        mBaselineView = baselineView;
+
         if (isWrapContentWidth) {
             // Width already has left padding in it since it was calculated by looking at
             // the right of each child view
@@ -616,25 +633,24 @@
             }
         }
 
-        // Use the bottom-most laid out view as the baseline.
-        View baselineView = null;
-        int baseline = 0;
-        for (int i = 0; i < count; i++) {
-            final View child = getChildAt(i);
-            if (child.getVisibility() != GONE) {
-                final int childBaseline = child.getBaseline();
-                if (childBaseline >= baseline) {
-                    baselineView = child;
-                    baseline = childBaseline;
-                }
-            }
-        }
-        mBaselineView = baselineView;
-
         setMeasuredDimension(width, height);
     }
 
     /**
+     * @return a negative number if the top of {@code p1} is above the top of
+     *         {@code p2} or if they have identical top values and the left of
+     *         {@code p1} is to the left of {@code p2}, or a positive number
+     *         otherwise
+     */
+    private int compareLayoutPosition(LayoutParams p1, LayoutParams p2) {
+        final int topDiff = p1.mTop - p2.mTop;
+        if (topDiff != 0) {
+            return topDiff;
+        }
+        return p1.mLeft - p2.mLeft;
+    }
+
+    /**
      * Measure a child. The child should have left, top, right and bottom information
      * stored in its LayoutParams. If any of these values is VALUE_NOT_SET it means
      * that the view can extend up to the corresponding edge.
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.mk b/core/jni/Android.mk
index 97744ea..30a7e68 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -18,6 +18,8 @@
 
 LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
 
+LOCAL_CFLAGS += -DU_USING_ICU_NAMESPACE=0
+
 LOCAL_SRC_FILES:= \
     AndroidRuntime.cpp \
     com_android_internal_content_NativeLibraryHelper.cpp \
@@ -41,25 +43,25 @@
     android_database_SQLiteDebug.cpp \
     android_emoji_EmojiFactory.cpp \
     android_view_DisplayEventReceiver.cpp \
-    android_view_Surface.cpp \
-    android_view_SurfaceControl.cpp \
-    android_view_SurfaceSession.cpp \
-    android_view_TextureView.cpp \
+    android_view_DisplayListCanvas.cpp \
+    android_view_GraphicBuffer.cpp \
+    android_view_HardwareLayer.cpp \
     android_view_InputChannel.cpp \
     android_view_InputDevice.cpp \
     android_view_InputEventReceiver.cpp \
     android_view_InputEventSender.cpp \
     android_view_InputQueue.cpp \
-    android_view_KeyEvent.cpp \
     android_view_KeyCharacterMap.cpp \
-    android_view_GraphicBuffer.cpp \
-    android_view_GLES20Canvas.cpp \
-    android_view_HardwareLayer.cpp \
-    android_view_ThreadedRenderer.cpp \
+    android_view_KeyEvent.cpp \
     android_view_MotionEvent.cpp \
     android_view_PointerIcon.cpp \
     android_view_RenderNode.cpp \
     android_view_RenderNodeAnimator.cpp \
+    android_view_Surface.cpp \
+    android_view_SurfaceControl.cpp \
+    android_view_SurfaceSession.cpp \
+    android_view_TextureView.cpp \
+    android_view_ThreadedRenderer.cpp \
     android_view_VelocityTracker.cpp \
     android_text_AndroidCharacter.cpp \
     android_text_AndroidBidi.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 77afd05..ad52e3f 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -127,16 +127,16 @@
 extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env);
 extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env);
 extern int register_android_view_DisplayEventReceiver(JNIEnv* env);
+extern int register_android_view_DisplayListCanvas(JNIEnv* env);
+extern int register_android_view_GraphicBuffer(JNIEnv* env);
+extern int register_android_view_HardwareLayer(JNIEnv* env);
 extern int register_android_view_RenderNode(JNIEnv* env);
 extern int register_android_view_RenderNodeAnimator(JNIEnv* env);
-extern int register_android_view_GraphicBuffer(JNIEnv* env);
-extern int register_android_view_GLES20Canvas(JNIEnv* env);
-extern int register_android_view_HardwareLayer(JNIEnv* env);
-extern int register_android_view_ThreadedRenderer(JNIEnv* env);
 extern int register_android_view_Surface(JNIEnv* env);
 extern int register_android_view_SurfaceControl(JNIEnv* env);
 extern int register_android_view_SurfaceSession(JNIEnv* env);
 extern int register_android_view_TextureView(JNIEnv* env);
+extern int register_android_view_ThreadedRenderer(JNIEnv* env);
 extern int register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper(JNIEnv *env);
 extern int register_android_database_CursorWindow(JNIEnv* env);
 extern int register_android_database_SQLiteConnection(JNIEnv* env);
@@ -1179,7 +1179,7 @@
     REG_JNI(register_android_view_RenderNode),
     REG_JNI(register_android_view_RenderNodeAnimator),
     REG_JNI(register_android_view_GraphicBuffer),
-    REG_JNI(register_android_view_GLES20Canvas),
+    REG_JNI(register_android_view_DisplayListCanvas),
     REG_JNI(register_android_view_HardwareLayer),
     REG_JNI(register_android_view_ThreadedRenderer),
     REG_JNI(register_android_view_Surface),
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index a6f19b1..e5ae147 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -48,10 +48,10 @@
             delete mBreakIterator;
         }
 
-        void setLocale(const Locale& locale) {
+        void setLocale(const icu::Locale& locale) {
             delete mBreakIterator;
             UErrorCode status = U_ZERO_ERROR;
-            mBreakIterator = BreakIterator::createLineInstance(locale, status);
+            mBreakIterator = icu::BreakIterator::createLineInstance(locale, status);
             // TODO: check status
         }
 
@@ -77,13 +77,13 @@
             }
         }
 
-        BreakIterator* breakIterator() const {
+        icu::BreakIterator* breakIterator() const {
             return mBreakIterator;
         }
 
     private:
         const size_t MAX_TEXT_BUF_RETAIN = 32678;
-        BreakIterator* mBreakIterator = nullptr;
+        icu::BreakIterator* mBreakIterator = nullptr;
         UText mUText = UTEXT_INITIALIZER;
         std::vector<uint16_t>mTextBuf;
 };
@@ -560,9 +560,9 @@
     // TODO: this array access is pretty inefficient, but we'll replace it anyway
     ScopedFloatArrayRO widthsScopedArr(env, widths);
 
-    BreakIterator* breakIterator = b->breakIterator();
+    icu::BreakIterator* breakIterator = b->breakIterator();
     int loc = breakIterator->first();
-    while ((loc = breakIterator->next()) != BreakIterator::DONE) {
+    while ((loc = breakIterator->next()) != icu::BreakIterator::DONE) {
         breaks.push_back(loc);
     }
 
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_DisplayListCanvas.cpp
similarity index 82%
rename from core/jni/android_view_GLES20Canvas.cpp
rename to core/jni/android_view_DisplayListCanvas.cpp
index 0bee7ae..f2e6c4b 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_DisplayListCanvas.cpp
@@ -50,43 +50,43 @@
 // Setup
 // ----------------------------------------------------------------------------
 
-static void android_view_GLES20Canvas_setViewport(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_setViewport(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jint width, jint height) {
     DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     renderer->setViewport(width, height);
 }
 
-static void android_view_GLES20Canvas_setHighContrastText(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_setHighContrastText(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jboolean highContrastText) {
     DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     renderer->setHighContrastText(highContrastText);
 }
 
-static void android_view_GLES20Canvas_insertReorderBarrier(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_insertReorderBarrier(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jboolean reorderEnable) {
     DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     renderer->insertReorderBarrier(reorderEnable);
 }
 
-static void android_view_GLES20Canvas_prepare(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_prepare(JNIEnv* env, jobject clazz,
         jlong rendererPtr) {
     DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     renderer->prepare();
 }
 
-static void android_view_GLES20Canvas_prepareDirty(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_prepareDirty(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jint left, jint top, jint right, jint bottom) {
     DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     renderer->prepareDirty(left, top, right, bottom);
 }
 
-static void android_view_GLES20Canvas_finish(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_finish(JNIEnv* env, jobject clazz,
         jlong rendererPtr) {
     DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     renderer->finish();
 }
 
-static void android_view_GLES20Canvas_setProperty(JNIEnv* env,
+static void android_view_DisplayListCanvas_setProperty(JNIEnv* env,
         jobject clazz, jstring name, jstring value) {
     if (!Caches::hasInstance()) {
         ALOGW("can't set property, no Caches instance");
@@ -108,7 +108,7 @@
 // Functor
 // ----------------------------------------------------------------------------
 
-static void android_view_GLES20Canvas_callDrawGLFunction(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_callDrawGLFunction(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jlong functorPtr) {
     DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     Functor* functor = reinterpret_cast<Functor*>(functorPtr);
@@ -120,11 +120,11 @@
 // Misc
 // ----------------------------------------------------------------------------
 
-static jint android_view_GLES20Canvas_getMaxTextureWidth(JNIEnv* env, jobject clazz) {
+static jint android_view_DisplayListCanvas_getMaxTextureWidth(JNIEnv* env, jobject clazz) {
     return Caches::getInstance().maxTextureSize;
 }
 
-static jint android_view_GLES20Canvas_getMaxTextureHeight(JNIEnv* env, jobject clazz) {
+static jint android_view_DisplayListCanvas_getMaxTextureHeight(JNIEnv* env, jobject clazz) {
     return Caches::getInstance().maxTextureSize;
 }
 
@@ -132,7 +132,7 @@
 // Drawing
 // ----------------------------------------------------------------------------
 
-static void android_view_GLES20Canvas_drawPatch(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_drawPatch(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jlong bitmapPtr, jlong patchPtr,
         float left, float top, float right, float bottom, jlong paintPtr) {
     SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapPtr);
@@ -143,7 +143,7 @@
     renderer->drawPatch(bitmap, patch, left, top, right, bottom, paint);
 }
 
-static void android_view_GLES20Canvas_drawRoundRectProps(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_drawRoundRectProps(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jlong leftPropPtr, jlong topPropPtr, jlong rightPropPtr,
         jlong bottomPropPtr, jlong rxPropPtr, jlong ryPropPtr, jlong paintPropPtr) {
     DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
@@ -157,7 +157,7 @@
     renderer->drawRoundRect(leftProp, topProp, rightProp, bottomProp, rxProp, ryProp, paintProp);
 }
 
-static void android_view_GLES20Canvas_drawCircleProps(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_drawCircleProps(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jlong xPropPtr, jlong yPropPtr, jlong radiusPropPtr, jlong paintPropPtr) {
     DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     CanvasPropertyPrimitive* xProp = reinterpret_cast<CanvasPropertyPrimitive*>(xPropPtr);
@@ -167,7 +167,7 @@
     renderer->drawCircle(xProp, yProp, radiusProp, paintProp);
 }
 
-static void android_view_GLES20Canvas_drawRegionAsRects(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_drawRegionAsRects(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jlong regionPtr, jlong paintPtr) {
     DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     SkRegion* region = reinterpret_cast<SkRegion*>(regionPtr);
@@ -201,17 +201,17 @@
 // Display lists
 // ----------------------------------------------------------------------------
 
-static jlong android_view_GLES20Canvas_finishRecording(JNIEnv* env,
+static jlong android_view_DisplayListCanvas_finishRecording(JNIEnv* env,
         jobject clazz, jlong rendererPtr) {
     DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     return reinterpret_cast<jlong>(renderer->finishRecording());
 }
 
-static jlong android_view_GLES20Canvas_createDisplayListRenderer(JNIEnv* env, jobject clazz) {
+static jlong android_view_DisplayListCanvas_createDisplayListRenderer(JNIEnv* env, jobject clazz) {
     return reinterpret_cast<jlong>(new DisplayListRenderer);
 }
 
-static void android_view_GLES20Canvas_drawRenderNode(JNIEnv* env,
+static void android_view_DisplayListCanvas_drawRenderNode(JNIEnv* env,
         jobject clazz, jlong rendererPtr, jlong renderNodePtr,
         jint flags) {
     DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
@@ -224,7 +224,7 @@
 // Layers
 // ----------------------------------------------------------------------------
 
-static void android_view_GLES20Canvas_drawLayer(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_drawLayer(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jlong layerPtr, jfloat x, jfloat y) {
     DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerPtr);
@@ -235,7 +235,7 @@
 // Common
 // ----------------------------------------------------------------------------
 
-static jboolean android_view_GLES20Canvas_isAvailable(JNIEnv* env, jobject clazz) {
+static jboolean android_view_DisplayListCanvas_isAvailable(JNIEnv* env, jobject clazz) {
     char prop[PROPERTY_VALUE_MAX];
     if (property_get("ro.kernel.qemu", prop, NULL) == 0) {
         // not in the emulator
@@ -261,36 +261,36 @@
 // JNI Glue
 // ----------------------------------------------------------------------------
 
-const char* const kClassPathName = "android/view/GLES20Canvas";
+const char* const kClassPathName = "android/view/DisplayListCanvas";
 
 static JNINativeMethod gMethods[] = {
-    { "nIsAvailable",       "()Z",             (void*) android_view_GLES20Canvas_isAvailable },
-    { "nSetViewport",       "(JII)V",          (void*) android_view_GLES20Canvas_setViewport },
-    { "nSetHighContrastText","(JZ)V",          (void*) android_view_GLES20Canvas_setHighContrastText },
-    { "nInsertReorderBarrier","(JZ)V",         (void*) android_view_GLES20Canvas_insertReorderBarrier },
-    { "nPrepare",           "(J)V",            (void*) android_view_GLES20Canvas_prepare },
-    { "nPrepareDirty",      "(JIIII)V",        (void*) android_view_GLES20Canvas_prepareDirty },
-    { "nFinish",            "(J)V",            (void*) android_view_GLES20Canvas_finish },
+    { "nIsAvailable",       "()Z",             (void*) android_view_DisplayListCanvas_isAvailable },
+    { "nSetViewport",       "(JII)V",          (void*) android_view_DisplayListCanvas_setViewport },
+    { "nSetHighContrastText","(JZ)V",          (void*) android_view_DisplayListCanvas_setHighContrastText },
+    { "nInsertReorderBarrier","(JZ)V",         (void*) android_view_DisplayListCanvas_insertReorderBarrier },
+    { "nPrepare",           "(J)V",            (void*) android_view_DisplayListCanvas_prepare },
+    { "nPrepareDirty",      "(JIIII)V",        (void*) android_view_DisplayListCanvas_prepareDirty },
+    { "nFinish",            "(J)V",            (void*) android_view_DisplayListCanvas_finish },
     { "nSetProperty",       "(Ljava/lang/String;Ljava/lang/String;)V",
-            (void*) android_view_GLES20Canvas_setProperty },
+            (void*) android_view_DisplayListCanvas_setProperty },
 
-    { "nCallDrawGLFunction", "(JJ)V",          (void*) android_view_GLES20Canvas_callDrawGLFunction },
+    { "nCallDrawGLFunction", "(JJ)V",          (void*) android_view_DisplayListCanvas_callDrawGLFunction },
 
-    { "nDrawPatch",         "(JJJFFFFJ)V",     (void*) android_view_GLES20Canvas_drawPatch },
+    { "nDrawPatch",         "(JJJFFFFJ)V",     (void*) android_view_DisplayListCanvas_drawPatch },
 
-    { "nDrawRects",         "(JJJ)V",          (void*) android_view_GLES20Canvas_drawRegionAsRects },
-    { "nDrawRoundRect",     "(JJJJJJJJ)V",     (void*) android_view_GLES20Canvas_drawRoundRectProps },
-    { "nDrawCircle",        "(JJJJJ)V",        (void*) android_view_GLES20Canvas_drawCircleProps },
+    { "nDrawRects",         "(JJJ)V",          (void*) android_view_DisplayListCanvas_drawRegionAsRects },
+    { "nDrawRoundRect",     "(JJJJJJJJ)V",     (void*) android_view_DisplayListCanvas_drawRoundRectProps },
+    { "nDrawCircle",        "(JJJJJ)V",        (void*) android_view_DisplayListCanvas_drawCircleProps },
 
-    { "nFinishRecording",   "(J)J",            (void*) android_view_GLES20Canvas_finishRecording },
-    { "nDrawRenderNode",    "(JJI)V",          (void*) android_view_GLES20Canvas_drawRenderNode },
+    { "nFinishRecording",   "(J)J",            (void*) android_view_DisplayListCanvas_finishRecording },
+    { "nDrawRenderNode",    "(JJI)V",          (void*) android_view_DisplayListCanvas_drawRenderNode },
 
-    { "nCreateDisplayListRenderer", "()J",     (void*) android_view_GLES20Canvas_createDisplayListRenderer },
+    { "nCreateDisplayListRenderer", "()J",     (void*) android_view_DisplayListCanvas_createDisplayListRenderer },
 
-    { "nDrawLayer",               "(JJFF)V",   (void*) android_view_GLES20Canvas_drawLayer },
+    { "nDrawLayer",               "(JJFF)V",   (void*) android_view_DisplayListCanvas_drawLayer },
 
-    { "nGetMaximumTextureWidth",  "()I",       (void*) android_view_GLES20Canvas_getMaxTextureWidth },
-    { "nGetMaximumTextureHeight", "()I",       (void*) android_view_GLES20Canvas_getMaxTextureHeight },
+    { "nGetMaximumTextureWidth",  "()I",       (void*) android_view_DisplayListCanvas_getMaxTextureWidth },
+    { "nGetMaximumTextureHeight", "()I",       (void*) android_view_DisplayListCanvas_getMaxTextureHeight },
 };
 
 static JNINativeMethod gActivityThreadMethods[] = {
@@ -298,7 +298,7 @@
                                                (void*) android_app_ActivityThread_dumpGraphics }
 };
 
-int register_android_view_GLES20Canvas(JNIEnv* env) {
+int register_android_view_DisplayListCanvas(JNIEnv* env) {
     jclass clazz = FindClassOrDie(env, "android/graphics/Rect");
     gRectClassInfo.set = GetMethodIDOrDie(env, clazz, "set", "(IIII)V");
 
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/docs/html/images/training/geofence.png b/docs/html/images/training/geofence.png
new file mode 100644
index 0000000..2d5d3aa
--- /dev/null
+++ b/docs/html/images/training/geofence.png
Binary files differ
diff --git a/docs/html/images/training/geofence@2x.png b/docs/html/images/training/geofence@2x.png
new file mode 100644
index 0000000..2f83105
--- /dev/null
+++ b/docs/html/images/training/geofence@2x.png
Binary files differ
diff --git a/docs/html/training/location/geofencing.jd b/docs/html/training/location/geofencing.jd
index 748b6ec..59fc4c6 100644
--- a/docs/html/training/location/geofencing.jd
+++ b/docs/html/training/location/geofencing.jd
@@ -9,9 +9,11 @@
 
 <h2>This lesson teaches you to</h2>
 <ol>
-    <li><a href="#RequestGeofences">Request Geofence Monitoring</a></li>
+    <li><a href="#RequestGeofences">Set up for Geofence Monitoring</a></li>
+    <li><a href="#CreateAdd">Create and Add Geofences</a></li>
     <li><a href="#HandleGeofenceTransitions">Handle Geofence Transitions</a></li>
     <li><a href="#StopGeofenceMonitoring">Stop Geofence Monitoring</a></li>
+
 </ol>
 
 <h2>You should also read</h2>
@@ -23,577 +25,148 @@
 
 <h2>Try it out</h2>
 
-<div class="download-box">
-  <a href="http://developer.android.com/shareables/training/GeofenceDetection.zip" class="button">Download the sample</a>
-  <p class="filename">GeofenceDetection.zip</p>
-</div>
+   <ul>
+      <li>
+        <a href="https://github.com/googlesamples/android-play-location/tree/master/Geofencing"
+        class="external-link">Geofencing</a>
+      </li>
+    </ul>
 
 </div>
 </div>
 <p>
-    Geofencing combines awareness of the user's current location with awareness of nearby
-    features, defined as the user's proximity to locations that may be of interest. To mark a
+    Geofencing combines awareness of the user's current location with awareness of the user's
+    proximity to locations that may be of interest. To mark a
     location of interest, you specify its latitude and longitude. To adjust the proximity for the
-    location, you add a radius. The latitude, longitude, and radius define a geofence.
-    You can have multiple active geofences at one time.
+    location, you add a radius. The latitude, longitude, and radius define a geofence, creating a
+    circular area, or fence, around the location of interest.
 </p>
 <p>
-    Location Services treats a geofences as an area rather than as a points and proximity. This
-    allows it to detect when the user enters or exits a geofence. For each geofence, you can ask
-    Location Services to send you entrance events or exit events or both. You can also limit the
-    duration of a geofence by specifying an expiration duration in milliseconds. After the geofence
-    expires, Location Services automatically removes it.
+    You can have multiple active geofences, with a limit of 100 per device user. For each geofence,
+    you can ask Location Services to send you entrance and exit events, or you can specify a
+    duration within the geofence area to wait, or <em>dwell</em>, before triggering an event. You
+    can limit the duration of any geofence by specifying an expiration duration in milliseconds.
+    After the geofence expires, Location Services automatically removes it.
 </p>
-<!--
-    Send geofences to Location Services
- -->
-<h2 id="RequestGeofences">Request Geofence Monitoring</h2>
+
+<img src="{@docRoot}images/training/geofence@2x.png"
+srcset="{@docRoot}images/training/geofence.png 1x, {@docRoot}images/training/geofence@2x.png 2x" alt=""
+  width="400" height="400"/>
+<p>
+    This lesson shows you how to add and remove geofences, and then listen for geofence transitions
+    using an {@link android.app.IntentService}.</p>
+
+<h2 id="RequestGeofences">Set up for Geofence Monitoring</h2>
 <p>
     The first step in requesting geofence monitoring is to request the necessary permission.
     To use geofencing, your app must request
     {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION}. To request this
     permission, add the following element as a child element of the
 <code><a href="{@docRoot}guide/topics/manifest/manifest-element.html">&lt;manifest&gt;</a></code>
-    element:
+    element in your app manifest:
 </p>
 <pre>
 &lt;uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/&gt;
 </pre>
-<!-- Check for Google Play services -->
-<h3>Check for Google Play Services</h3>
-<p>
-    Location Services is part of the Google Play services APK. Since it's hard to anticipate the
-    state of the user's device, you should always check that the APK is installed before you attempt
-    to connect to Location Services. To check that the APK is installed, call
-<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesUtil.html#isGooglePlayServicesAvailable(android.content.Context)">GooglePlayServicesUtil.isGooglePlayServicesAvailable()</a></code>,
-    which returns one of the
-    integer result codes listed in the API reference documentation. If you encounter an error,
-    call
-<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesUtil.html#getErrorDialog(int, android.app.Activity, int)">GooglePlayServicesUtil.getErrorDialog()</a></code>
-    to retrieve localized dialog that prompts users to take the correct action, then display
-    the dialog in a {@link android.support.v4.app.DialogFragment}. The dialog may allow the
-    user to correct the problem, in which case Google Play services may send a result back to your
-    activity. To handle this result, override the method
-    {@link android.support.v4.app.FragmentActivity#onActivityResult onActivityResult()}
 
-</p>
-<p class="note">
-    <strong>Note:</strong> To make your app compatible with
-    platform version 1.6 and later, the activity that displays the
-    {@link android.support.v4.app.DialogFragment} must subclass
-    {@link android.support.v4.app.FragmentActivity} instead of {@link android.app.Activity}. Using
-    {@link android.support.v4.app.FragmentActivity} also allows you to call
-    {@link android.support.v4.app.FragmentActivity#getSupportFragmentManager
-    getSupportFragmentManager()} to display the {@link android.support.v4.app.DialogFragment}.
-</p>
 <p>
-    Since you usually need to check for Google Play services in more than one place in your code,
-    define a method that encapsulates the check, then call the method before each connection
-    attempt. The following snippet contains all of the code required to check for Google
-    Play services:
+    If you want to use an {@link android.app.IntentService} to listen for geofence transitions,
+    add an element specifying the service name. This element must be
+    a child of the <code><a href="{@docRoot}guide/topics/manifest/application-element.html">
+    &lt;application&gt;</a></code> element:
+</p>
+
+<pre>
+&lt;application
+   android:allowBackup=&quot;true&quot;&gt;
+   ...
+   &lt;service android:name=".GeofenceTransitionsIntentService"/&gt;
+&lt;application/&gt;
+</pre>
+
+<p>To access the location APIs, you need to create an instance of the
+  Google Play services API client. To learn how to connect your client, see
+  <a href="{@docRoot}training/location/retrieve-current.html#play-services">Connect
+  to Google Play Services</a>.</p>
+
+<h2 id="CreateAdd">Create and Add Geofences</h2>
+
+<p>Your app needs to create and add geofences using the location API's builder class for
+ creating Geofence objects, and the convenience class for adding them. Also, to handle the
+ intents sent from Location Services when geofence transitions occur, you can define a
+ {@link android.app.PendingIntent} as shown in this section.
+</p>
+
+<h3>Create geofence objects</h3>
+
+<p>
+    First, use <code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.Builder.
+    html">Geofence.Builder</a></code> to create a geofence, setting the desired radius, duration, and
+    transition types for the geofence. For example, to populate a list object named
+    {@code mGeofenceList}:
+    </p>
+
+<pre>
+mGeofenceList.add(new Geofence.Builder()
+    // Set the request ID of the geofence. This is a string to identify this
+    // geofence.
+    .setRequestId(entry.getKey())
+
+    .setCircularRegion(
+            entry.getValue().latitude,
+            entry.getValue().longitude,
+            Constants.GEOFENCE_RADIUS_IN_METERS
+    )
+    .setExpirationDuration(Constants.GEOFENCE_EXPIRATION_IN_MILLISECONDS)
+    .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER |
+            Geofence.GEOFENCE_TRANSITION_EXIT)
+    .build());
+</pre>
+
+<p>This example pulls data from a constants file. In actual practice, apps might
+    dynamically create geofences based on the user's location.</p>
+
+<h3>Specify geofences and initial triggers</h3>
+
+<p>
+    The following snippet uses the <code><a href="{@docRoot}reference/com/google/android/gms/location/GeofencingRequest.html">
+    GeofencingRequest</a></code> class
+    and its nested <code><a href="{@docRoot}reference/com/google/android/gms/location/GeofencingRequest.Builder.html">
+    GeofencingRequestBuilder</a></code> class to
+    specify the geofences to monitor and to set how related geofence events are triggered:
 </p>
 <pre>
-public class MainActivity extends FragmentActivity {
-    ...
-    // Global constants
-    /*
-     * Define a request code to send to Google Play services
-     * This code is returned in Activity.onActivityResult
-     */
-    private final static int
-            CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000;
-    ...
-    // Define a DialogFragment that displays the error dialog
-    public static class ErrorDialogFragment extends DialogFragment {
-        // Global field to contain the error dialog
-        private Dialog mDialog;
-        ...
-        // Default constructor. Sets the dialog field to null
-        public ErrorDialogFragment() {
-            super();
-            mDialog = null;
-        }
-        ...
-        // Set the dialog to display
-        public void setDialog(Dialog dialog) {
-            mDialog = dialog;
-        }
-        ...
-        // Return a Dialog to the DialogFragment.
-        &#64;Override
-        public Dialog onCreateDialog(Bundle savedInstanceState) {
-            return mDialog;
-        }
-        ...
-    }
-    ...
-    /*
-     * Handle results returned to the FragmentActivity
-     * by Google Play services
-     */
-     &#64;Override
-    protected void onActivityResult(
-            int requestCode, int resultCode, Intent data) {
-        // Decide what to do based on the original request code
-        switch (requestCode) {
-            ...
-            case CONNECTION_FAILURE_RESOLUTION_REQUEST :
-            /*
-             * If the result code is Activity.RESULT_OK, try
-             * to connect again
-             */
-                switch (resultCode) {
-                    ...
-                    case Activity.RESULT_OK :
-                    /*
-                     * Try the request again
-                     */
-                    ...
-                    break;
-                }
-            ...
-        }
-        ...
-    }
-    ...
-    private boolean servicesConnected() {
-        // Check that Google Play services is available
-        int resultCode =
-                GooglePlayServicesUtil.
-                        isGooglePlayServicesAvailable(this);
-        // If Google Play services is available
-        if (ConnectionResult.SUCCESS == resultCode) {
-            // In debug mode, log the status
-            Log.d("Geofence Detection",
-                    "Google Play services is available.");
-            // Continue
-            return true;
-        // Google Play services was not available for some reason
-        } else {
-            // Get the error code
-            int errorCode = connectionResult.getErrorCode();
-            // Get the error dialog from Google Play services
-            Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(
-                    errorCode,
-                    this,
-                    CONNECTION_FAILURE_RESOLUTION_REQUEST);
-
-            // If Google Play services can provide an error dialog
-            if (errorDialog != null) {
-                // Create a new DialogFragment for the error dialog
-                ErrorDialogFragment errorFragment =
-                        new ErrorDialogFragment();
-                // Set the dialog in the DialogFragment
-                errorFragment.setDialog(errorDialog);
-                // Show the error dialog in the DialogFragment
-                errorFragment.show(
-                        getSupportFragmentManager(),
-                        "Geofence Detection");
-            }
-        }
-    }
-    ...
+private GeofencingRequest getGeofencingRequest() {
+    GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
+    builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
+    builder.addGeofences(mGeofenceList);
+    return builder.build();
 }
 </pre>
-<p>
-    Snippets in the following sections call this method to verify that Google Play services is
-    available.
-</p>
-<p>
-    To use geofencing, start by defining the geofences you want to monitor. Although you usually
-    store geofence data in a local database or download it from the network, you need to send
-    a geofence to Location Services as an instance of
-<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code>,
-    which you create with
-<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.Builder.html">Geofence.Builder</a></code>.
-    Each
-<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code>
-    object contains the following information:
-</p>
-<dl>
-    <dt>Latitude, longitude, and radius</dt>
-    <dd>
-        Define a circular area for the geofence. Use the latitude and longitude to mark a location
-        of interest, and then use the radius to adjust how close the user needs to approach the
-        location before the geofence is detected. The larger the radius, the more likely the
-        user will trigger a geofence transition alert by approaching the geofence. For example,
-        providing a large radius for a geofencing app that turns on lights in the user's house as
-        the user returns home might cause the lights to go on even if the user is simply passing by.
-    </dd>
-    <dt>Expiration time</dt>
-    <dd>
-        How long the geofence should remain active. Once the expiration time is reached, Location
-        Services deletes the geofence. Most of the time, you should specify an expiration time, but
-        you may want to keep permanent geofences for the user's home or place of work.
-    </dd>
-    <dt>Transition type</dt>
-    <dd>
-        Location Services can detect when the user steps within the radius of the geofence ("entry")
-        and when the user steps outside the radius of the geofence ("exit"), or both.
-    </dd>
-    <dt>Geofence ID</dt>
-    <dd>
-        A string that is stored with the geofence. You should make this unique, so that you can
-        use it to remove a geofence from Location Services tracking.
-    </dd>
-</dl>
-<h3>Define geofence storage</h3>
-<p>
-    A geofencing app needs to read and write geofence data to persistent storage. You shouldn't use
-<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code>
-    objects to do this; instead, use storage techniques such as databases that can store groups of
-    related data.
-</p>
-<p>
-    As an example of storing geofence data, the following snippet defines two classes that use
-    the app's {@link android.content.SharedPreferences} instance for persistent storage. The class
-    {@code SimpleGeofence}, analogous to a database record, stores the
-    data for a single
-<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code>
-    object in a "flattened" form. The class {@code SimpleGeofenceStore}, analogous to a database,
-    reads and writes {@code SimpleGeofence} data to the
-    {@link android.content.SharedPreferences} instance.
-</p>
-<pre>
-public class MainActivity extends FragmentActivity {
-    ...
-    /**
-     * A single Geofence object, defined by its center and radius.
-     */
-    public class SimpleGeofence {
-            // Instance variables
-            private final String mId;
-            private final double mLatitude;
-            private final double mLongitude;
-            private final float mRadius;
-            private long mExpirationDuration;
-            private int mTransitionType;
 
-        /**
-         * @param geofenceId The Geofence's request ID
-         * @param latitude Latitude of the Geofence's center.
-         * @param longitude Longitude of the Geofence's center.
-         * @param radius Radius of the geofence circle.
-         * @param expiration Geofence expiration duration
-         * @param transition Type of Geofence transition.
-         */
-        public SimpleGeofence(
-                String geofenceId,
-                double latitude,
-                double longitude,
-                float radius,
-                long expiration,
-                int transition) {
-            // Set the instance fields from the constructor
-            this.mId = geofenceId;
-            this.mLatitude = latitude;
-            this.mLongitude = longitude;
-            this.mRadius = radius;
-            this.mExpirationDuration = expiration;
-            this.mTransitionType = transition;
-        }
-        // Instance field getters
-        public String getId() {
-            return mId;
-        }
-        public double getLatitude() {
-            return mLatitude;
-        }
-        public double getLongitude() {
-            return mLongitude;
-        }
-        public float getRadius() {
-            return mRadius;
-        }
-        public long getExpirationDuration() {
-            return mExpirationDuration;
-        }
-        public int getTransitionType() {
-            return mTransitionType;
-        }
-        /**
-         * Creates a Location Services Geofence object from a
-         * SimpleGeofence.
-         *
-         * @return A Geofence object
-         */
-        public Geofence toGeofence() {
-            // Build a new Geofence object
-            return new Geofence.Builder()
-                    .setRequestId(getId())
-                    .setTransitionTypes(mTransitionType)
-                    .setCircularRegion(
-                            getLatitude(), getLongitude(), getRadius())
-                    .setExpirationDuration(mExpirationDuration)
-                    .build();
-        }
-    }
-    ...
-    /**
-     * Storage for geofence values, implemented in SharedPreferences.
-     */
-    public class SimpleGeofenceStore {
-        // Keys for flattened geofences stored in SharedPreferences
-        public static final String KEY_LATITUDE =
-                "com.example.android.geofence.KEY_LATITUDE";
-        public static final String KEY_LONGITUDE =
-                "com.example.android.geofence.KEY_LONGITUDE";
-        public static final String KEY_RADIUS =
-                "com.example.android.geofence.KEY_RADIUS";
-        public static final String KEY_EXPIRATION_DURATION =
-                "com.example.android.geofence.KEY_EXPIRATION_DURATION";
-        public static final String KEY_TRANSITION_TYPE =
-                "com.example.android.geofence.KEY_TRANSITION_TYPE";
-        // The prefix for flattened geofence keys
-        public static final String KEY_PREFIX =
-                "com.example.android.geofence.KEY";
-        /*
-         * Invalid values, used to test geofence storage when
-         * retrieving geofences
-         */
-        public static final long INVALID_LONG_VALUE = -999l;
-        public static final float INVALID_FLOAT_VALUE = -999.0f;
-        public static final int INVALID_INT_VALUE = -999;
-        // The SharedPreferences object in which geofences are stored
-        private final SharedPreferences mPrefs;
-        // The name of the SharedPreferences
-        private static final String SHARED_PREFERENCES =
-                "SharedPreferences";
-        // Create the SharedPreferences storage with private access only
-        public SimpleGeofenceStore(Context context) {
-            mPrefs =
-                    context.getSharedPreferences(
-                            SHARED_PREFERENCES,
-                            Context.MODE_PRIVATE);
-        }
-        /**
-         * Returns a stored geofence by its id, or returns {@code null}
-         * if it's not found.
-         *
-         * @param id The ID of a stored geofence
-         * @return A geofence defined by its center and radius. See
-         */
-        public SimpleGeofence getGeofence(String id) {
-            /*
-             * Get the latitude for the geofence identified by id, or
-             * INVALID_FLOAT_VALUE if it doesn't exist
-             */
-            double lat = mPrefs.getFloat(
-                    getGeofenceFieldKey(id, KEY_LATITUDE),
-                    INVALID_FLOAT_VALUE);
-            /*
-             * Get the longitude for the geofence identified by id, or
-             * INVALID_FLOAT_VALUE if it doesn't exist
-             */
-            double lng = mPrefs.getFloat(
-                    getGeofenceFieldKey(id, KEY_LONGITUDE),
-                    INVALID_FLOAT_VALUE);
-            /*
-             * Get the radius for the geofence identified by id, or
-             * INVALID_FLOAT_VALUE if it doesn't exist
-             */
-            float radius = mPrefs.getFloat(
-                    getGeofenceFieldKey(id, KEY_RADIUS),
-                    INVALID_FLOAT_VALUE);
-            /*
-             * Get the expiration duration for the geofence identified
-             * by id, or INVALID_LONG_VALUE if it doesn't exist
-             */
-            long expirationDuration = mPrefs.getLong(
-                    getGeofenceFieldKey(id, KEY_EXPIRATION_DURATION),
-                    INVALID_LONG_VALUE);
-            /*
-             * Get the transition type for the geofence identified by
-             * id, or INVALID_INT_VALUE if it doesn't exist
-             */
-            int transitionType = mPrefs.getInt(
-                    getGeofenceFieldKey(id, KEY_TRANSITION_TYPE),
-                    INVALID_INT_VALUE);
-            // If none of the values is incorrect, return the object
-            if (
-                lat != GeofenceUtils.INVALID_FLOAT_VALUE &amp;&amp;
-                lng != GeofenceUtils.INVALID_FLOAT_VALUE &amp;&amp;
-                radius != GeofenceUtils.INVALID_FLOAT_VALUE &amp;&amp;
-                expirationDuration !=
-                        GeofenceUtils.INVALID_LONG_VALUE &amp;&amp;
-                transitionType != GeofenceUtils.INVALID_INT_VALUE) {
-
-                // Return a true Geofence object
-                return new SimpleGeofence(
-                        id, lat, lng, radius, expirationDuration,
-                        transitionType);
-            // Otherwise, return null.
-            } else {
-                return null;
-            }
-        }
-        /**
-         * Save a geofence.
-         * @param geofence The SimpleGeofence containing the
-         * values you want to save in SharedPreferences
-         */
-        public void setGeofence(String id, SimpleGeofence geofence) {
-            /*
-             * Get a SharedPreferences editor instance. Among other
-             * things, SharedPreferences ensures that updates are atomic
-             * and non-concurrent
-             */
-            Editor editor = mPrefs.edit();
-            // Write the Geofence values to SharedPreferences
-            editor.putFloat(
-                    getGeofenceFieldKey(id, KEY_LATITUDE),
-                    (float) geofence.getLatitude());
-            editor.putFloat(
-                    getGeofenceFieldKey(id, KEY_LONGITUDE),
-                    (float) geofence.getLongitude());
-            editor.putFloat(
-                    getGeofenceFieldKey(id, KEY_RADIUS),
-                    geofence.getRadius());
-            editor.putLong(
-                    getGeofenceFieldKey(id, KEY_EXPIRATION_DURATION),
-                    geofence.getExpirationDuration());
-            editor.putInt(
-                    getGeofenceFieldKey(id, KEY_TRANSITION_TYPE),
-                    geofence.getTransitionType());
-            // Commit the changes
-            editor.commit();
-        }
-        public void clearGeofence(String id) {
-            /*
-             * Remove a flattened geofence object from storage by
-             * removing all of its keys
-             */
-            Editor editor = mPrefs.edit();
-            editor.remove(getGeofenceFieldKey(id, KEY_LATITUDE));
-            editor.remove(getGeofenceFieldKey(id, KEY_LONGITUDE));
-            editor.remove(getGeofenceFieldKey(id, KEY_RADIUS));
-            editor.remove(getGeofenceFieldKey(id,
-                    KEY_EXPIRATION_DURATION));
-            editor.remove(getGeofenceFieldKey(id, KEY_TRANSITION_TYPE));
-            editor.commit();
-        }
-        /**
-         * Given a Geofence object's ID and the name of a field
-         * (for example, KEY_LATITUDE), return the key name of the
-         * object's values in SharedPreferences.
-         *
-         * @param id The ID of a Geofence object
-         * @param fieldName The field represented by the key
-         * @return The full key name of a value in SharedPreferences
-         */
-        private String getGeofenceFieldKey(String id,
-                String fieldName) {
-            return KEY_PREFIX + "_" + id + "_" + fieldName;
-        }
-    }
-    ...
-}
-</pre>
-<h3>Create Geofence objects</h3>
 <p>
-    The following snippet uses the {@code SimpleGeofence} and {@code SimpleGeofenceStore} classes
-    gets geofence data from the UI, stores it in {@code SimpleGeofence} objects, stores these
-    objects in a {@code SimpleGeofenceStore} object, and then creates
-<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code>
-    objects:
+    This example shows the use of two geofence triggers. The <code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html#GEOFENCE_TRANSITION_ENTER">
+    GEOFENCE_TRANSITION_ENTER</a></code>
+    transition triggers when a device enters a geofence, and the <code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html#GEOFENCE_TRANSITION_EXIT">
+    GEOFENCE_TRANSITION_EXIT</a></code>
+    transition triggers when a device exits a geofence. Specifying
+    <code><a href="{@docRoot}reference/com/google/android/gms/location/GeofencingRequest.html#INITIAL_TRIGGER_ENTER">
+        INITIAL_TRIGGER_ENTER</a></code> tells Location services that
+    <code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html#GEOFENCE_TRANSITION_ENTER">
+        GEOFENCE_TRANSITION_ENTER</a></code>
+    should be triggered if the the device is already inside the geofence.</p>
 </p>
-<pre>
-public class MainActivity extends FragmentActivity {
-    ...
-    /*
-     * Use to set an expiration time for a geofence. After this amount
-     * of time Location Services will stop tracking the geofence.
-     */
-    private static final long SECONDS_PER_HOUR = 60;
-    private static final long MILLISECONDS_PER_SECOND = 1000;
-    private static final long GEOFENCE_EXPIRATION_IN_HOURS = 12;
-    private static final long GEOFENCE_EXPIRATION_TIME =
-            GEOFENCE_EXPIRATION_IN_HOURS *
-            SECONDS_PER_HOUR *
-            MILLISECONDS_PER_SECOND;
-    ...
-    /*
-     * Handles to UI views containing geofence data
-     */
-    // Handle to geofence 1 latitude in the UI
-    private EditText mLatitude1;
-    // Handle to geofence 1 longitude in the UI
-    private EditText mLongitude1;
-    // Handle to geofence 1 radius in the UI
-    private EditText mRadius1;
-    // Handle to geofence 2 latitude in the UI
-    private EditText mLatitude2;
-    // Handle to geofence 2 longitude in the UI
-    private EditText mLongitude2;
-    // Handle to geofence 2 radius in the UI
-    private EditText mRadius2;
-    /*
-     * Internal geofence objects for geofence 1 and 2
-     */
-    private SimpleGeofence mUIGeofence1;
-    private SimpleGeofence mUIGeofence2;
-    ...
-    // Internal List of Geofence objects
-    List&lt;Geofence&gt; mGeofenceList;
-    // Persistent storage for geofences
-    private SimpleGeofenceStore mGeofenceStorage;
-    ...
-    &#64;Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        ...
-        // Instantiate a new geofence storage area
-        mGeofenceStorage = new SimpleGeofenceStore(this);
 
-        // Instantiate the current List of geofences
-        mCurrentGeofences = new ArrayList&lt;Geofence&gt;();
-    }
-    ...
-    /**
-     * Get the geofence parameters for each geofence from the UI
-     * and add them to a List.
-     */
-    public void createGeofences() {
-        /*
-         * Create an internal object to store the data. Set its
-         * ID to "1". This is a "flattened" object that contains
-         * a set of strings
-         */
-        mUIGeofence1 = new SimpleGeofence(
-                "1",
-                Double.valueOf(mLatitude1.getText().toString()),
-                Double.valueOf(mLongitude1.getText().toString()),
-                Float.valueOf(mRadius1.getText().toString()),
-                GEOFENCE_EXPIRATION_TIME,
-                // This geofence records only entry transitions
-                Geofence.GEOFENCE_TRANSITION_ENTER);
-        // Store this flat version
-        mGeofenceStorage.setGeofence("1", mUIGeofence1);
-        // Create another internal object. Set its ID to "2"
-        mUIGeofence2 = new SimpleGeofence(
-                "2",
-                Double.valueOf(mLatitude2.getText().toString()),
-                Double.valueOf(mLongitude2.getText().toString()),
-                Float.valueOf(mRadius2.getText().toString()),
-                GEOFENCE_EXPIRATION_TIME,
-                // This geofence records both entry and exit transitions
-                Geofence.GEOFENCE_TRANSITION_ENTER |
-                Geofence.GEOFENCE_TRANSITION_EXIT);
-        // Store this flat version
-        mGeofenceStorage.setGeofence(2, mUIGeofence2);
-        mGeofenceList.add(mUIGeofence1.toGeofence());
-        mGeofenceList.add(mUIGeofence2.toGeofence());
-    }
-    ...
-}
-</pre>
-<p>
-    In addition to the {@link java.util.List} of
-<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code>
-    objects you want to monitor, you need to provide Location Services with the
-    {@link android.content.Intent} that it sends to your app when it detects geofence
-    transitions.
-<h4>Define a Intent for geofence transitions</h4>
+<p>In many cases, it may be preferable to use instead <code><a href="{@docRoot}reference/com/google/android/gms/location/GeofencingRequest.html#INITIAL_TRIGGER_DWELL">
+    INITIAL_TRIGGER_DWELL</a></code>,
+    which triggers events only when the user stops for a defined duration within a geofence.
+    This approach can help reduce "alert spam" resulting from large numbers notifications when a
+    device briefly enters and exits geofences. Another strategy for getting best results from your
+    geofences is to set a minimum radius of 100 meters. This helps account for the location accuracy
+    of typical WiFi networks, and also helps reduce device power consumption.
+</p>
+
+<h3>Define an Intent for geofence transitions</h3>
 <p>
     The {@link android.content.Intent} sent from Location Services can trigger various actions in
     your app, but you should <i>not</i> have it start an activity or fragment, because components
@@ -601,807 +174,133 @@
     {@link android.app.IntentService} is a good way to handle the intent. An
     {@link android.app.IntentService} can post a notification, do long-running background work,
     send intents to other services, or send a broadcast intent. The following snippet shows how
-    how to define a {@link android.app.PendingIntent} that starts an
-    {@link android.app.IntentService}:
+    to define a {@link android.app.PendingIntent} that starts an {@link android.app.IntentService}:
 </p>
 <pre>
 public class MainActivity extends FragmentActivity {
     ...
-    /*
-     * Create a PendingIntent that triggers an IntentService in your
-     * app when a geofence transition occurs.
-     */
-    private PendingIntent getTransitionPendingIntent() {
-        // Create an explicit Intent
-        Intent intent = new Intent(this,
-                ReceiveTransitionsIntentService.class);
-        /*
-         * Return the PendingIntent
-         */
-        return PendingIntent.getService(
-                this,
-                0,
-                intent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
-    }
-    ...
-}
-</pre>
-<p>
-    Now you have all the code you need to send a request to monitor geofences to Location
-    Services.
-</p>
-<!-- Send the monitoring request -->
-<h3 id="requestmonitoring">Send the monitoring request</h3>
-<p>
-    Sending the monitoring request requires two asynchronous operations. The first operation gets a
-    location client for the request, and the second makes the request using the client. In both
-    cases, Location Services invokes a callback method when it finishes the operation. The best way
-    to handle these operations is to chain together the method calls. The following snippets
-    demonstrate how to set up an activity, define the methods, and call them in the proper order.
-</p>
-<p>
-     First, modify the activity's class definition to implement the necessary callback interfaces.
-     Add the following interfaces:
-</p>
-<dl>
-    <dt>
-<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html">ConnectionCallbacks</a></code>
-    </dt>
-    <dd>
-        Specifies methods that Location Services calls when a location client is connected or
-        disconnected.
-    </dd>
-    <dt>
-<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.OnConnectionFailedListener.html">OnConnectionFailedListener</a></code>
-    </dt>
-    <dd>
-        Specifies a method that Location Services calls if an error occurs while attempting to
-        connect the location client.
-    </dd>
-    <dt>
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnAddGeofencesResultListener.html">OnAddGeofencesResultListener</a></code>
-    </dt>
-    <dd>
-        Specifies a method that Location Services calls once it has added the geofences.
-    </dd>
-</dl>
-<p>
-    For example:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
-        ConnectionCallbacks,
-        OnConnectionFailedListener,
-        OnAddGeofencesResultListener {
-    ...
-}
-</pre>
-<h4>Start the request process</h4>
-<p>
-    Next, define a method that starts the request process by connecting to Location Services.
-    Mark this as a request to add a geofence by setting a global variable. This allows you to
-    use the callback
-<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)">ConnectionCallbacks.onConnected()</a></code>
-    to add geofences and to remove them, as described in succeeding sections.
-</p>
-<p>
-<p>
-    To guard against race conditions that might arise if your app tries to start another request
-    before the first one finishes, define a boolean flag that tracks the state of the current
-    request:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
-        ConnectionCallbacks,
-        OnConnectionFailedListener,
-        OnAddGeofencesResultListener {
-    ...
-    // Holds the location client
-    private LocationClient mLocationClient;
-    // Stores the PendingIntent used to request geofence monitoring
-    private PendingIntent mGeofenceRequestIntent;
-    // Defines the allowable request types.
-    public enum REQUEST_TYPE = {ADD}
-    private REQUEST_TYPE mRequestType;
-    // Flag that indicates if a request is underway.
-    private boolean mInProgress;
-    ...
-    &#64;Override
-    protected void onCreate(Bundle savedInstanceState) {
-        ...
-        // Start with the request flag set to false
-        mInProgress = false;
-        ...
-    }
-    ...
-    /**
-     * Start a request for geofence monitoring by calling
-     * LocationClient.connect().
-     */
-    public void addGeofences() {
-        // Start a request to add geofences
-        mRequestType = ADD;
-        /*
-         * Test for Google Play services after setting the request type.
-         * If Google Play services isn't present, the proper request
-         * can be restarted.
-         */
-        if (!servicesConnected()) {
-            return;
+    private PendingIntent getGeofencePendingIntent() {
+        // Reuse the PendingIntent if we already have it.
+        if (mGeofencePendingIntent != null) {
+            return mGeofencePendingIntent;
         }
-        /*
-         * Create a new location client object. Since the current
-         * activity class implements ConnectionCallbacks and
-         * OnConnectionFailedListener, pass the current activity object
-         * as the listener for both parameters
-         */
-        mLocationClient = new LocationClient(this, this, this)
-        // If a request is not already underway
-        if (!mInProgress) {
-            // Indicate that a request is underway
-            mInProgress = true;
-            // Request a connection from the client to Location Services
-            mLocationClient.connect();
-        } else {
-            /*
-             * A request is already underway. You can handle
-             * this situation by disconnecting the client,
-             * re-setting the flag, and then re-trying the
-             * request.
-             */
-        }
+        Intent intent = new Intent(this, GeofenceTransitionsIntentService.class);
+        // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when
+        // calling addGeofences() and removeGeofences().
+        return PendingIntent.getService(this, 0, intent, PendingIntent.
+                FLAG_UPDATE_CURRENT);
     }
-    ...
-}
 </pre>
-<h4>Send a request to add the geofences</h4>
+
+<h3>Add geofences</h3>
+
 <p>
-    In your implementation of
-<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)">ConnectionCallbacks.onConnected()</a></code>,
-    call
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#addGeofences(java.util.List<com.google.android.gms.location.Geofence>, android.app.PendingIntent, com.google.android.gms.location.LocationClient.OnAddGeofencesResultListener)">LocationClient.addGeofences()</a></code>.
-    Notice that if the connection fails,
-<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)">onConnected()</a></code>
-    isn't called, and the request stops.
+    To add geofences, use the <code><a href="{@docRoot}reference/com/google/android/gms/location/GeofencingApi.html#addGeofences(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.location.GeofencingRequest, android.app.PendingIntent)">{@code GeoencingApi.addGeofences()}</a></code> method.
+    Provide the Google API client, the <code><a href="{@docRoot}reference/com/google/android/gms/location/GeofencingRequest">
+    GeofencingRequest</a></code> object, and the {@link android.app.PendingIntent}.
+    The following snippet, which processes the results in <code><a href="{@docRoot}reference/com/google/android/gms/common/api/ResultCallback.html#onResult(R)">
+    onResult()</a></code>, assumes that the main activity implements <code><a href="{@docRoot}reference/com/google/android/gms/common/api/ResultCallback.html">
+    ResultCallback</a></code>:
 </p>
 <pre>
-public class MainActivity extends FragmentActivity implements
-        ConnectionCallbacks,
-        OnConnectionFailedListener,
-        OnAddGeofencesResultListener {
+public class MainActivity extends FragmentActivity {
     ...
-    /*
-     * Provide the implementation of ConnectionCallbacks.onConnected()
-     * Once the connection is available, send a request to add the
-     * Geofences
-     */
-    &#64;Override
-    private void onConnected(Bundle dataBundle) {
-        ...
-        switch (mRequestType) {
-            case ADD :
-                // Get the PendingIntent for the request
-                mTransitionPendingIntent =
-                        getTransitionPendingIntent();
-                // Send a request to add the current geofences
-                mLocationClient.addGeofences(
-                        mCurrentGeofences, pendingIntent, this);
-            ...
-        }
-    }
-    ...
-}
+    LocationServices.GeofencingApi.addGeofences(
+                mGoogleApiClient,
+                getGeofencingRequest(),
+                getGeofencePendingIntent()
+        ).setResultCallback(this);
 </pre>
-<p>
-    Notice that
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#addGeofences(java.util.List<com.google.android.gms.location.Geofence>, android.app.PendingIntent, com.google.android.gms.location.LocationClient.OnAddGeofencesResultListener)">addGeofences()</a></code>
-    returns immediately, but the status of the request is indeterminate until Location Services
-    calls
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnAddGeofencesResultListener.html#onAddGeofencesResult(int, java.lang.String[])">onAddGeofencesResult()</a></code>
-    Once this method is called, you can determine if the request was successful or not.
-</p>
-<h4>Check the result returned by Location Services</h4>
-<p>
-    When Location Services invokes your implementation of the callback method
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnAddGeofencesResultListener.html#onAddGeofencesResult(int, java.lang.String[])">onAddGeofencesResult()</a></code>,
-    indicating that the request is complete, examine the incoming status code. If the request
-    was successful, the geofences you requested are active. If the request was unsuccessful,
-    the geofences aren't active, and you need to re-try the request or report an error. For example:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
-        ConnectionCallbacks,
-        OnConnectionFailedListener,
-        OnAddGeofencesResultListener {
-        ...
-    /*
-     * Provide the implementation of
-     * OnAddGeofencesResultListener.onAddGeofencesResult.
-     * Handle the result of adding the geofences
-     *
-     */
-    &#64;Override
-    public void onAddGeofencesResult(
-            int statusCode, String[] geofenceRequestIds) {
-        // If adding the geofences was successful
-        if (LocationStatusCodes.SUCCESS == statusCode) {
-            /*
-             * Handle successful addition of geofences here.
-             * You can send out a broadcast intent or update the UI.
-             * geofences into the Intent's extended data.
-             */
-        } else {
-        // If adding the geofences failed
-            /*
-             * Report errors here.
-             * You can log the error using Log.e() or update
-             * the UI.
-             */
-        }
-        // Turn off the in progress flag and disconnect the client
-        mInProgress = false;
-        mLocationClient.disconnect();
-    }
-    ...
-}
-</pre>
-<!-- Handle disconnections -->
-<h3>Handle disconnections</h3>
-<p>
-    In some cases, Location Services may disconnect from the activity recognition client before
-    you call
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#disconnect()">disconnect()</a></code>.
-    To handle this situation, implement <code>
-<a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html#onDisconnected()">onDisconnected()</a></code>.
-    In this method, set the request flag to indicate that a request is not in progress, and
-    delete the client:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
-        ConnectionCallbacks,
-        OnConnectionFailedListener,
-        OnAddGeofencesResultListener {
-    ...
-    /*
-     * Implement ConnectionCallbacks.onDisconnected()
-     * Called by Location Services once the location client is
-     * disconnected.
-     */
-    &#64;Override
-    public void onDisconnected() {
-        // Turn off the request flag
-        mInProgress = false;
-        // Destroy the current location client
-        mLocationClient = null;
-    }
-    ...
-}
-</pre>
-<!-- Handle connection errors -->
-<h3>Handle connection errors</h3>
-<p>
-    Besides handling the normal callbacks from Location Services, you have to provide a callback
-    method that Location Services calls if a connection error occurs. This callback method
-    can re-use the {@link android.support.v4.app.DialogFragment} class that you defined to
-    handle the check for Google Play services. It can also re-use the override you defined
-    for {@link android.support.v4.app.FragmentActivity#onActivityResult onActivityResult()} that
-    receives any Google Play services results that occur when the user interacts with the
-    error dialog. The following snippet shows you a sample implementation of the callback method:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
-        ConnectionCallbacks,
-        OnConnectionFailedListener,
-        OnAddGeofencesResultListener {
-    ...
-    // Implementation of OnConnectionFailedListener.onConnectionFailed
-    &#64;Override
-    public void onConnectionFailed(ConnectionResult connectionResult) {
-        // Turn off the request flag
-        mInProgress = false;
-        /*
-         * If the error has a resolution, start a Google Play services
-         * activity to resolve it.
-         */
-        if (connectionResult.hasResolution()) {
-            try {
-                connectionResult.startResolutionForResult(
-                        this,
-                        CONNECTION_FAILURE_RESOLUTION_REQUEST);
-            } catch (SendIntentException e) {
-                // Log the error
-                e.printStackTrace();
-            }
-        // If no resolution is available, display an error dialog
-        } else {
-            // Get the error code
-            int errorCode = connectionResult.getErrorCode();
-            // Get the error dialog from Google Play services
-            Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(
-                    errorCode,
-                    this,
-                    CONNECTION_FAILURE_RESOLUTION_REQUEST);
-            // If Google Play services can provide an error dialog
-            if (errorDialog != null) {
-                // Create a new DialogFragment for the error dialog
-                ErrorDialogFragment errorFragment =
-                        new ErrorDialogFragment();
-                // Set the dialog in the DialogFragment
-                errorFragment.setDialog(errorDialog);
-                // Show the error dialog in the DialogFragment
-                errorFragment.show(
-                        getSupportFragmentManager(),
-                        "Geofence Detection");
-            }
-        }
-    }
-    ...
-}
-</pre>
-<!--
-    Handle Geofence Transitions
- -->
+
+
 <h2 id="HandleGeofenceTransitions">Handle Geofence Transitions</h2>
 <p>
     When Location Services detects that the user has entered or exited a geofence, it
     sends out the {@link android.content.Intent} contained in the {@link android.app.PendingIntent}
-    you included in the request to add geofences. This {@link android.content.Intent} is
+    you included in the request to add geofences. This {@link android.content.Intent} is received
+    by a service like <code>GeofenceTransitionsIntentService</code>,
+    which obtains the geofencing event from the intent, determines the type of Geofence transition(s),
+    and determines which of the defined geofences was triggered. It then sends a notification as
+    the output.
 </p>
-<h3>Define an IntentService</h3>
 <p>
     The following snippet shows how to define an {@link android.app.IntentService} that posts a
     notification when a geofence transition occurs. When the user clicks the notification, the
     app's main activity appears:
 </p>
 <pre>
-public class ReceiveTransitionsIntentService extends IntentService {
-    ...
-    /**
-     * Sets an identifier for the service
-     */
-    public ReceiveTransitionsIntentService() {
-        super("ReceiveTransitionsIntentService");
-    }
-    /**
-     * Handles incoming intents
-     *&#64;param intent The Intent sent by Location Services. This
-     * Intent is provided
-     * to Location Services (inside a PendingIntent) when you call
-     * addGeofences()
-     */
-    &#64;Override
+public class GeofenceTransitionsIntentService extends IntentService {
+   ...
     protected void onHandleIntent(Intent intent) {
-        // First check for errors
-        if (LocationClient.hasError(intent)) {
-            // Get the error code with a static method
-            int errorCode = LocationClient.getErrorCode(intent);
-            // Log the error
-            Log.e("ReceiveTransitionsIntentService",
-                    "Location Services error: " +
-                    Integer.toString(errorCode));
-            /*
-             * You can also send the error code to an Activity or
-             * Fragment with a broadcast Intent
-             */
-        /*
-         * If there's no error, get the transition type and the IDs
-         * of the geofence or geofences that triggered the transition
-         */
-        } else {
-            // Get the type of transition (entry or exit)
-            int transitionType =
-                    LocationClient.getGeofenceTransition(intent);
-            // Test that a valid transition was reported
-            if (
-                (transitionType == Geofence.GEOFENCE_TRANSITION_ENTER)
-                 ||
-                (transitionType == Geofence.GEOFENCE_TRANSITION_EXIT)
-               ) {
-                List &lt;Geofence&gt; triggerList =
-                        getTriggeringGeofences(intent);
+        GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
+        if (geofencingEvent.hasError()) {
+            String errorMessage = GeofenceErrorMessages.getErrorString(this,
+                    geofencingEvent.getErrorCode());
+            Log.e(TAG, errorMessage);
+            return;
+        }
 
-                String[] triggerIds = new String[geofenceList.size()];
+        // Get the transition type.
+        int geofenceTransition = geofencingEvent.getGeofenceTransition();
 
-                for (int i = 0; i &lt; triggerIds.length; i++) {
-                    // Store the Id of each geofence
-                    triggerIds[i] = triggerList.get(i).getRequestId();
-                }
-                /*
-                 * At this point, you can store the IDs for further use
-                 * display them, or display the details associated with
-                 * them.
-                 */
-            }
-        // An invalid transition was reported
+        // Test that the reported transition was of interest.
+        if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
+                geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
+
+            // Get the geofences that were triggered. A single event can trigger
+            // multiple geofences.
+            List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();
+
+            // Get the transition details as a String.
+            String geofenceTransitionDetails = getGeofenceTransitionDetails(
+                    this,
+                    geofenceTransition,
+                    triggeringGeofences
+            );
+
+            // Send notification and log the transition details.
+            sendNotification(geofenceTransitionDetails);
+            Log.i(TAG, geofenceTransitionDetails);
         } else {
-            Log.e("ReceiveTransitionsIntentService",
-                    "Geofence transition error: " +
-                    Integer.toString()transitionType));
+            // Log the error.
+            Log.e(TAG, getString(R.string.geofence_transition_invalid_type,
+                    geofenceTransition));
         }
     }
-    ...
-}
 </pre>
-<!-- Specify the IntentService in the manifest -->
-<h3>Specify the IntentService in the manifest</h3>
-<p>
-    To identify the {@link android.app.IntentService} to the system, add a
-    <code><a href="{@docRoot}guide/topics/manifest/service-element.html">&lt;service&gt;</a></code>
-    element to the app manifest. For example:
-</p>
-<pre>
-&lt;service
-    android:name="com.example.android.location.ReceiveTransitionsIntentService"
-    android:label="&#64;string/app_name"
-    android:exported="false"&gt;
-&lt;/service&gt;
-</pre>
-<p>
-    Notice that you don't have to specify intent filters for the service, because it only receives
-    explicit intents. How the incoming geofence transition intents are created is described in the
-    section <a href="#requestmonitoring">Send the monitoring request</a>.
-</p>
+
+<p>After detecting the transition event via the {@link android.app.PendingIntent},
+    this {@link android.app.IntentService} gets the geofence transition type and tests whether
+    it is one of the events the app uses to trigger notifications -- either
+    <code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html#GEOFENCE_TRANSITION_ENTER">GEOFENCE_TRANSITION_ENTER</a></code>
+     or <code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html#GEOFENCE_TRANSITION_EXIT">GEOFENCE_TRANSITION_EXIT</a></code>
+    in this case. The service then sends a notification and logs the transition details.</p>
 <!--
     Remove Geofences
  -->
 <h2 id="StopGeofenceMonitoring">Stop Geofence Monitoring</h2>
-<p>
-    To stop geofence monitoring, you remove the geofences themselves. You can remove a specific
-    set of geofences or all the geofences associated with a {@link android.app.PendingIntent}. The
-    procedure is similar to adding geofences. The first operation gets a location
-    client for the removal request, and the second makes the request using the client.
+
+<p>Stopping geofence monitoring when it is no longer needed or desired can help save battery
+    power and CPU cycles on the device. You can stop geofence monitoring
+    in the main activity used to add and remove geofences; removing a geofence stops it
+    immediately. The API provides methods to
+    remove geofences either by request IDs, or by removing geofences associated with a given
+    {@link android.app.PendingIntent}.
 </p>
 <p>
-    The callback methods that Location Services invokes when it has finished removing geofences
-    are defined in the interface
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html">LocationClient.OnRemoveGeofencesResultListener</a></code>. Declare
-    this interface as part of your class definition, and then add definitions for its two methods:
-</p>
-<dl>
-    <dt>
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html#onRemoveGeofencesByPendingIntentResult(int, android.app.PendingIntent)">onRemoveGeofencesByPendingIntentResult()</a></code>
-    </dt>
-    <dd>
-        Callback invoked when Location Services finishes a request to remove all geofences made
-        by the method
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#removeGeofences(android.app.PendingIntent, com.google.android.gms.location.LocationClient.OnRemoveGeofencesResultListener)">removeGeofences(PendingIntent, LocationClient.OnRemoveGeofencesResultListener)</a></code>.
-    </dd>
-    <dt>
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html#onRemoveGeofencesByRequestIdsResult(int, java.lang.String[])">onRemoveGeofencesByRequestIdsResult(List&lt;String&gt;, LocationClient.OnRemoveGeofencesResultListener)</a></code>
-    </dt>
-    <dd>
-        Callback invoked when Location Services finished a request to remove a set of geofences,
-        specified by their geofence IDs, by the method
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#removeGeofences(java.util.List<java.lang.String>, com.google.android.gms.location.LocationClient.OnRemoveGeofencesResultListener)">removeGeofences(List&lt;String&gt;, LocationClient.OnRemoveGeofencesResultListener)</a></code>.
-    </dd>
-</dl>
-<p>
-    Examples of implementing these methods are shown in the next snippets.
-</p>
-<h3>Remove all geofences</h3>
-<p>
-    Since removing geofences uses some of the methods you use to add geofences, start by defining
-    another request type:
+    The following snippet removes geofences by {@link android.app.PendingIntent}, stopping all
+    further notification when the device enters or exits previously added geofences:
 </p>
 <pre>
-public class MainActivity extends FragmentActivity implements
-        ConnectionCallbacks,
-        OnConnectionFailedListener,
-        OnAddGeofencesResultListener {
-    ...
-    // Enum type for controlling the type of removal requested
-    public enum REQUEST_TYPE = {ADD, REMOVE_INTENT}
-    ...
+LocationServices.GeofencingApi.removeGeofences(
+            mGoogleApiClient,
+            // This is the same pending intent that was used in addGeofences().
+            getGeofencePendingIntent()
+    ).setResultCallback(this); // Result processed in onResult().
 }
 </pre>
+
 <p>
-    Start the removal request by getting a connection to Location Services. If the connection fails,
-<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)">onConnected()</a></code> isn't called,
-    and the request stops. The following snippet shows how to start the request:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
-        ConnectionCallbacks,
-        OnConnectionFailedListener,
-        OnAddGeofencesResultListener {
-    ...
-    /**
-     * Start a request to remove geofences by calling
-     * LocationClient.connect()
-     */
-    public void removeGeofences(PendingIntent requestIntent) {
-        // Record the type of removal request
-        mRequestType = REMOVE_INTENT;
-        /*
-         * Test for Google Play services after setting the request type.
-         * If Google Play services isn't present, the request can be
-         * restarted.
-         */
-        if (!servicesConnected()) {
-            return;
-        }
-        // Store the PendingIntent
-        mGeofenceRequestIntent = requestIntent;
-        /*
-         * Create a new location client object. Since the current
-         * activity class implements ConnectionCallbacks and
-         * OnConnectionFailedListener, pass the current activity object
-         * as the listener for both parameters
-         */
-        mLocationClient = new LocationClient(this, this, this);
-        // If a request is not already underway
-        if (!mInProgress) {
-            // Indicate that a request is underway
-            mInProgress = true;
-            // Request a connection from the client to Location Services
-            mLocationClient.connect();
-        } else {
-            /*
-             * A request is already underway. You can handle
-             * this situation by disconnecting the client,
-             * re-setting the flag, and then re-trying the
-             * request.
-             */
-        }
-    }
-    ...
-}
-</pre>
-<p>
-   When Location Services invokes the callback method indicating that the connection is open,
-   make the request to remove all geofences. Disconnect the client after making the request.
-   For example:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
-        ConnectionCallbacks,
-        OnConnectionFailedListener,
-        OnAddGeofencesResultListener {
-    ...
-    /**
-     * Once the connection is available, send a request to remove the
-     * Geofences. The method signature used depends on which type of
-     * remove request was originally received.
-     */
-    private void onConnected(Bundle dataBundle) {
-        /*
-         * Choose what to do based on the request type set in
-         * removeGeofences
-         */
-        switch (mRequestType) {
-            ...
-            case REMOVE_INTENT :
-                mLocationClient.removeGeofences(
-                        mGeofenceRequestIntent, this);
-                break;
-            ...
-        }
-    }
-    ...
-}
-</pre>
-<p>
-    Although the call to
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#removeGeofences(android.app.PendingIntent, com.google.android.gms.location.LocationClient.OnRemoveGeofencesResultListener)">removeGeofences(PendingIntent, LocationClient.OnRemoveGeofencesResultListener)</a></code>    Services calls
-    returns immediately, the result of the removal request is indeterminate until Location Services
-    calls
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html#onRemoveGeofencesByPendingIntentResult(int, android.app.PendingIntent)">onRemoveGeofencesByPendingIntentResult()</a></code>.
-    The following snippet shows how to define this method:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
-        ConnectionCallbacks,
-        OnConnectionFailedListener,
-        OnAddGeofencesResultListener {
-    ...
-    /**
-     * When the request to remove geofences by PendingIntent returns,
-     * handle the result.
-     *
-     *&#64;param statusCode the code returned by Location Services
-     *&#64;param requestIntent The Intent used to request the removal.
-     */
-    &#64;Override
-    public void onRemoveGeofencesByPendingIntentResult(int statusCode,
-            PendingIntent requestIntent) {
-        // If removing the geofences was successful
-        if (statusCode == LocationStatusCodes.SUCCESS) {
-            /*
-             * Handle successful removal of geofences here.
-             * You can send out a broadcast intent or update the UI.
-             * geofences into the Intent's extended data.
-             */
-        } else {
-        // If adding the geocodes failed
-            /*
-             * Report errors here.
-             * You can log the error using Log.e() or update
-             * the UI.
-             */
-        }
-        /*
-         * Disconnect the location client regardless of the
-         * request status, and indicate that a request is no
-         * longer in progress
-         */
-        mInProgress = false;
-        mLocationClient.disconnect();
-    }
-    ...
-}
-</pre>
-<h3>Remove individual geofences</h3>
-<p>
-    The procedure for removing an individual geofence or set of geofences is similar to the
-    removal of all geofences. To specify the geofences you want remove, add their geofence ID
-    values to a {@link java.util.List} of String objects. Pass this {@link java.util.List} to a
-    different definition of {@code removeGeofences} with the appropriate signature. This method
-    then starts the removal process.
-</p>
-<p>
-    Start by adding a request type for removing geofences by a list, and also add a global variable
-    for storing the list of geofences:
-</p>
-<pre>
-    ...
-    // Enum type for controlling the type of removal requested
-    public enum REQUEST_TYPE = {ADD, REMOVE_INTENT, REMOVE_LIST}
-    // Store the list of geofence Ids to remove
-    String&lt;List&gt; mGeofencesToRemove;
-</pre>
-<p>
-    Next, define a list of geofences you want to remove. For example, this snippet removes the
-<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code>
-    defined by the geofence ID "1":
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
-        ConnectionCallbacks,
-        OnConnectionFailedListener,
-        OnAddGeofencesResultListener {
-    ...
-        List&lt;String&gt; listOfGeofences =
-                Collections.singletonList("1");
-        removeGeofences(listOfGeofences);
-    ...
-}
-</pre>
-<p>
-    The following snippet defines the {@code removeGeofences()} method:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
-        ConnectionCallbacks,
-        OnConnectionFailedListener,
-        OnAddGeofencesResultListener {
-    ...
-    /**
-     * Start a request to remove monitoring by
-     * calling LocationClient.connect()
-     *
-     */
-    public void removeGeofences(List&lt;String&gt; geofenceIds) {
-        // If Google Play services is unavailable, exit
-        // Record the type of removal request
-        mRequestType = REMOVE_LIST;
-        /*
-         * Test for Google Play services after setting the request type.
-         * If Google Play services isn't present, the request can be
-         * restarted.
-         */
-        if (!servicesConnected()) {
-            return;
-        }
-        // Store the list of geofences to remove
-        mGeofencesToRemove = geofenceIds;
-        /*
-         * Create a new location client object. Since the current
-         * activity class implements ConnectionCallbacks and
-         * OnConnectionFailedListener, pass the current activity object
-         * as the listener for both parameters
-         */
-        mLocationClient = new LocationClient(this, this, this);
-        // If a request is not already underway
-        if (!mInProgress) {
-            // Indicate that a request is underway
-            mInProgress = true;
-            // Request a connection from the client to Location Services
-            mLocationClient.connect();
-        } else {
-            /*
-             * A request is already underway. You can handle
-             * this situation by disconnecting the client,
-             * re-setting the flag, and then re-trying the
-             * request.
-             */
-        }
-    }
-    ...
-}
-</pre>
-<p>
-   When Location Services invokes the callback method indicating that the connection is open,
-   make the request to remove the list of geofences. Disconnect the client after making the request.
-   For example:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
-        ConnectionCallbacks,
-        OnConnectionFailedListener,
-        OnAddGeofencesResultListener {
-    ...
-    private void onConnected(Bundle dataBundle) {
-        ...
-        switch (mRequestType) {
-        ...
-        // If removeGeofencesById was called
-            case REMOVE_LIST :
-                mLocationClient.removeGeofences(
-                        mGeofencesToRemove, this);
-                break;
-        ...
-        }
-        ...
-    }
-    ...
-}
-</pre>
-<p>
-    Define an implementation of
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html#onRemoveGeofencesByRequestIdsResult(int, java.lang.String[])">onRemoveGeofencesByRequestIdsResult()</a></code>.
-    Location Services invokes this callback method to indicate that the request to remove a list of
-    geofences is complete. In this method, examine the incoming status code and take the
-    appropriate action:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
-        ConnectionCallbacks,
-        OnConnectionFailedListener,
-        OnAddGeofencesResultListener {
-    ...
-    /**
-     * When the request to remove geofences by IDs returns, handle the
-     * result.
-     *
-     * &#64;param statusCode The code returned by Location Services
-     * &#64;param geofenceRequestIds The IDs removed
-     */
-    &#64;Override
-    public void onRemoveGeofencesByRequestIdsResult(
-            int statusCode, String[] geofenceRequestIds) {
-        // If removing the geocodes was successful
-        if (LocationStatusCodes.SUCCESS == statusCode) {
-            /*
-             * Handle successful removal of geofences here.
-             * You can send out a broadcast intent or update the UI.
-             * geofences into the Intent's extended data.
-             */
-        } else {
-        // If removing the geofences failed
-            /*
-             * Report errors here.
-             * You can log the error using Log.e() or update
-             * the UI.
-             */
-        }
-        // Indicate that a request is no longer in progress
-        mInProgress = false;
-        // Disconnect the location client
-        mLocationClient.disconnect();
-    }
-    ...
-}
-</pre>
-<p>
-    You can combine geofencing with other location-aware features, such as periodic location updates
-    or activity recognition, which are described in other lessons in this class.
-</p>
-<p>
-    The next lesson,
-    <a href="activity-recognition.html">Recognizing the User's Current Activity</a>, shows you how
-    to request and receive activity updates. At regular intervals, Location Services can send you
-    information about the user's current physical activity. Based on this information, you can
-    change your app's behavior; for example, you can switch to a longer update interval if you
-    detect that the user is walking instead of driving.
+    You can combine geofencing with other location-aware features, such as periodic location updates.
+    For more information, see the other lessons in this class.
 </p>
diff --git a/docs/html/training/location/index.jd b/docs/html/training/location/index.jd
index 35e177f..c4dec99 100644
--- a/docs/html/training/location/index.jd
+++ b/docs/html/training/location/index.jd
@@ -81,4 +81,10 @@
     Learn how to convert a location's latitude and longitude into an address
     (reverse geocoding).
   </dd>
+  <dt>
+    <b><a href="geofencing.html">Creating and Monitoring Geofences</a></b>
+  </dt> <dd>
+    Learn how to define one or more geographic areas as locations of interest,
+    called geofences, and detect when the user is close to or inside a geofence.
+  </dd>
 </dl>
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs
index 89e72f1..11ae1a6 100644
--- a/docs/html/training/training_toc.cs
+++ b/docs/html/training/training_toc.cs
@@ -736,6 +736,10 @@
             Displaying a Location Address
           </a>
           </li>
+          <li><a href="<?cs var:toroot ?>training/location/geofencing.html">
+            Creating and Monitoring Geofences
+          </a>
+          </li>
         </ul>
       </li>
     </ul>
diff --git a/docs/html/wear/images/partners/acer.png b/docs/html/wear/images/partners/acer.png
new file mode 100644
index 0000000..439de80
--- /dev/null
+++ b/docs/html/wear/images/partners/acer.png
Binary files differ
diff --git a/docs/html/wear/images/partners/arm.png b/docs/html/wear/images/partners/arm.png
new file mode 100644
index 0000000..3e2f642
--- /dev/null
+++ b/docs/html/wear/images/partners/arm.png
Binary files differ
diff --git a/docs/html/wear/images/partners/huawei.png b/docs/html/wear/images/partners/huawei.png
new file mode 100644
index 0000000..9099ed4
--- /dev/null
+++ b/docs/html/wear/images/partners/huawei.png
Binary files differ
diff --git a/docs/html/wear/index.jd b/docs/html/wear/index.jd
index 27e8098..316f5ca 100644
--- a/docs/html/wear/index.jd
+++ b/docs/html/wear/index.jd
@@ -202,6 +202,12 @@
 
           <div class="landing-partners cols">
             <div class="col-4">
+              <img src="/wear/images/partners/acer.png" alt="Acer">
+            </div>
+            <div class="col-4">
+              <img src="/wear/images/partners/arm.png" alt="ARM">
+            </div>
+            <div class="col-4">
               <img src="/wear/images/partners/asus.png" alt="Asus">
             </div>
             <div class="col-4">
@@ -214,6 +220,9 @@
               <img src="/wear/images/partners/htc.png" alt="HTC">
             </div>
             <div class="col-4">
+              <img src="/wear/images/partners/huawei.png" alt="Huawei">
+            </div>
+            <div class="col-4">
               <img src="/wear/images/partners/intel.png" alt="Intel">
             </div>
             <div class="col-4">
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..a9ce7f4 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;
@@ -208,7 +208,8 @@
 // Fill
 ////////////////////////////////////////////////////////////////////////////////
 
-void GlopBuilder::setFill(int color, float alphaScale, SkXfermode::Mode mode,
+void GlopBuilder::setFill(int color, float alphaScale,
+        SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage,
         const SkShader* shader, const SkColorFilter* colorFilter) {
     if (mode != SkXfermode::kClear_Mode) {
         float alpha = (SkColorGetA(color) / 255.0f) * alphaScale;
@@ -226,18 +227,17 @@
     } else {
         mOutGlop->fill.color = { 0, 0, 0, 1 };
     }
-    const bool SWAP_SRC_DST = false;
 
     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)
             || PaintUtils::isBlendedColorFilter(colorFilter)
             || mode != SkXfermode::kSrcOver_Mode) {
         if (CC_LIKELY(mode <= SkXfermode::kScreen_Mode)) {
-            Blend::getFactors(mode, SWAP_SRC_DST,
+            Blend::getFactors(mode, modeUsage,
                     &mOutGlop->blend.src, &mOutGlop->blend.dst);
         } else {
             // These blend modes are not supported by OpenGL directly and have
@@ -247,11 +247,11 @@
             // back to the default SrcOver blend mode instead
             if (CC_UNLIKELY(mCaches.extensions().hasFramebufferFetch())) {
                 mDescription.framebufferMode = mode;
-                mDescription.swapSrcDst = SWAP_SRC_DST;
+                mDescription.swapSrcDst = (modeUsage == Blend::ModeOrderSwap::Swap);
                 // blending in shader, don't enable
             } else {
                 // unsupported
-                Blend::getFactors(SkXfermode::kSrcOver_Mode, SWAP_SRC_DST,
+                Blend::getFactors(SkXfermode::kSrcOver_Mode, modeUsage,
                         &mOutGlop->blend.src, &mOutGlop->blend.dst);
             }
         }
@@ -317,17 +317,17 @@
             color |= 0x00FFFFFF;
             shader = nullptr;
         }
-        setFill(color, alphaScale, PaintUtils::getXfermode(paint->getXfermode()),
+        setFill(color, alphaScale,
+                PaintUtils::getXfermode(paint->getXfermode()), Blend::ModeOrderSwap::NoSwap,
                 shader, paint->getColorFilter());
     } else {
         mOutGlop->fill.color = { alphaScale, alphaScale, alphaScale, alphaScale };
 
-        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,
+            Blend::getFactors(SkXfermode::kSrcOver_Mode, Blend::ModeOrderSwap::NoSwap,
                     &mOutGlop->blend.src, &mOutGlop->blend.dst);
         } else {
             mOutGlop->blend = { GL_ZERO, GL_ZERO };
@@ -349,7 +349,8 @@
 
     mOutGlop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
 
-    setFill(paint.getColor(), alphaScale, PaintUtils::getXfermode(paint.getXfermode()),
+    setFill(paint.getColor(), alphaScale,
+            PaintUtils::getXfermode(paint.getXfermode()), Blend::ModeOrderSwap::NoSwap,
             paint.getShader(), paint.getColorFilter());
     mDescription.modulate = mOutGlop->fill.color.a < 1.0f;
     return *this;
@@ -363,7 +364,8 @@
     //specify invalid filter/clamp, since these are always static for PathTextures
     mOutGlop->fill.texture = { &texture, GL_TEXTURE_2D, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
 
-    setFill(paint.getColor(), alphaScale, PaintUtils::getXfermode(paint.getXfermode()),
+    setFill(paint.getColor(), alphaScale,
+            PaintUtils::getXfermode(paint.getXfermode()), Blend::ModeOrderSwap::NoSwap,
             paint.getShader(), paint.getColorFilter());
 
     mDescription.hasAlpha8Texture = true;
@@ -386,7 +388,8 @@
         shadowColor &= paint.getColor() | COLOR_BITMASK;
     }
 
-    setFill(shadowColor, alphaScale, PaintUtils::getXfermode(paint.getXfermode()),
+    setFill(shadowColor, alphaScale,
+            PaintUtils::getXfermode(paint.getXfermode()), Blend::ModeOrderSwap::NoSwap,
             paint.getShader(), paint.getColorFilter());
 
     mDescription.hasAlpha8Texture = true;
@@ -399,7 +402,8 @@
     REQUIRE_STAGES(kMeshStage);
 
     mOutGlop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
-    setFill(SK_ColorBLACK, 1.0f, SkXfermode::kSrcOver_Mode, nullptr, nullptr);
+    setFill(SK_ColorBLACK, 1.0f, SkXfermode::kSrcOver_Mode, Blend::ModeOrderSwap::NoSwap,
+            nullptr, nullptr);
     return *this;
 }
 
@@ -408,12 +412,13 @@
     REQUIRE_STAGES(kMeshStage);
 
     mOutGlop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
-    setFill(SK_ColorBLACK, 1.0f, SkXfermode::kClear_Mode, nullptr, nullptr);
+    setFill(SK_ColorBLACK, 1.0f, SkXfermode::kClear_Mode, Blend::ModeOrderSwap::NoSwap,
+            nullptr, nullptr);
     return *this;
 }
 
 GlopBuilder& GlopBuilder::setFillLayer(Texture& texture, const SkColorFilter* colorFilter,
-        float alpha, SkXfermode::Mode mode) {
+        float alpha, SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage) {
     TRIGGER_STAGE(kFillStage);
     REQUIRE_STAGES(kMeshStage);
 
@@ -421,7 +426,7 @@
             GL_TEXTURE_2D, GL_LINEAR, GL_CLAMP_TO_EDGE, nullptr };
     mOutGlop->fill.color = { alpha, alpha, alpha, alpha };
 
-    setFill(SK_ColorWHITE, alpha, mode, nullptr, colorFilter);
+    setFill(SK_ColorWHITE, alpha, mode, modeUsage, nullptr, colorFilter);
 
     mDescription.modulate = mOutGlop->fill.color.a < 1.0f;
     return *this;
@@ -435,7 +440,8 @@
             layer.getRenderTarget(), GL_LINEAR, GL_CLAMP_TO_EDGE, &layer.getTexTransform() };
     mOutGlop->fill.color = { alpha, alpha, alpha, alpha };
 
-    setFill(SK_ColorWHITE, alpha, layer.getMode(), nullptr, layer.getColorFilter());
+    setFill(SK_ColorWHITE, alpha, layer.getMode(), Blend::ModeOrderSwap::NoSwap,
+            nullptr, layer.getColorFilter());
 
     mDescription.modulate = mOutGlop->fill.color.a < 1.0f;
     mDescription.hasTextureTransform = true;
@@ -540,12 +546,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 +575,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/GlopBuilder.h b/libs/hwui/GlopBuilder.h
index 74d4889..b335a2c 100644
--- a/libs/hwui/GlopBuilder.h
+++ b/libs/hwui/GlopBuilder.h
@@ -18,6 +18,7 @@
 
 #include "OpenGLRenderer.h"
 #include "Program.h"
+#include "renderstate/Blend.h"
 #include "utils/Macros.h"
 
 class SkPaint;
@@ -65,7 +66,7 @@
     GlopBuilder& setFillBlack();
     GlopBuilder& setFillClear();
     GlopBuilder& setFillLayer(Texture& texture, const SkColorFilter* colorFilter,
-            float alpha, SkXfermode::Mode mode);
+            float alpha, SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage);
     GlopBuilder& setFillTextureLayer(Layer& layer, float alpha);
 
     GlopBuilder& setTransform(const Matrix4& ortho, const Matrix4& transform, bool fudgingOffset);
@@ -94,7 +95,8 @@
 
     void build();
 private:
-    void setFill(int color, float alphaScale, SkXfermode::Mode mode,
+    void setFill(int color, float alphaScale,
+            SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage,
             const SkShader* shader, const SkColorFilter* colorFilter);
 
     enum StageFlags {
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index a6f6e18..622b570 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -908,15 +908,35 @@
         resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
     } else {
         EVENT_LOGD("composeHardwareLayerRect");
+
+        if (USE_GLOPS) {
+            Blend::ModeOrderSwap modeUsage = swap ?
+                    Blend::ModeOrderSwap::Swap : Blend::ModeOrderSwap::NoSwap;
+            const Matrix4& transform = swap ? Matrix4::identity() : *currentTransform();
+            bool snap = !swap
+                    && layer->getWidth() == static_cast<uint32_t>(rect.getWidth())
+                    && layer->getHeight() == static_cast<uint32_t>(rect.getHeight());
+            Glop glop;
+            GlopBuilder(mRenderState, mCaches, &glop)
+                    .setMeshTexturedUvQuad(nullptr, layer->texCoords)
+                    .setFillLayer(layer->getTexture(), layer->getColorFilter(), getLayerAlpha(layer), layer->getMode(), modeUsage)
+                    .setTransform(currentSnapshot()->getOrthoMatrix(), transform, false)
+                    .setModelViewMapUnitToRectOptionalSnap(snap, rect)
+                    .setRoundRectClipState(currentSnapshot()->roundRectClipState)
+                    .build();
+            renderGlop(glop);
+            return;
+        }
+
         const Rect& texCoords = layer->texCoords;
         resetDrawTextureTexCoords(texCoords.left, texCoords.top,
                 texCoords.right, texCoords.bottom);
 
         float x = rect.left;
         float y = rect.top;
-        bool simpleTransform = currentTransform()->isPureTranslate() &&
-                layer->getWidth() == (uint32_t) rect.getWidth() &&
-                layer->getHeight() == (uint32_t) rect.getHeight();
+        bool simpleTransform = currentTransform()->isPureTranslate()
+                && layer->getWidth() == (uint32_t) rect.getWidth()
+                && layer->getHeight() == (uint32_t) rect.getHeight();
 
         if (simpleTransform) {
             // When we're swapping, the layer is already in screen coordinates
@@ -1053,11 +1073,42 @@
         rects = safeRegion.getArray(&count);
     }
 
-    const float alpha = getLayerAlpha(layer);
     const float texX = 1.0f / float(layer->getWidth());
     const float texY = 1.0f / float(layer->getHeight());
     const float height = rect.getHeight();
 
+    if (USE_GLOPS) {
+        TextureVertex quadVertices[count * 4];
+        //std::unique_ptr<TextureVertex[]> quadVertices(new TextureVertex[count * 4]);
+        TextureVertex* mesh = &quadVertices[0];
+        for (size_t i = 0; i < count; i++) {
+            const android::Rect* r = &rects[i];
+
+            const float u1 = r->left * texX;
+            const float v1 = (height - r->top) * texY;
+            const float u2 = r->right * texX;
+            const float v2 = (height - r->bottom) * texY;
+
+            // TODO: Reject quads outside of the clip
+            TextureVertex::set(mesh++, r->left, r->top, u1, v1);
+            TextureVertex::set(mesh++, r->right, r->top, u2, v1);
+            TextureVertex::set(mesh++, r->left, r->bottom, u1, v2);
+            TextureVertex::set(mesh++, r->right, r->bottom, u2, v2);
+        }
+        Glop glop;
+        GlopBuilder(mRenderState, mCaches, &glop)
+                .setMeshTexturedIndexedQuads(&quadVertices[0], count * 6)
+                .setFillLayer(layer->getTexture(), layer->getColorFilter(), getLayerAlpha(layer), layer->getMode(), Blend::ModeOrderSwap::NoSwap)
+                .setTransform(currentSnapshot()->getOrthoMatrix(), *currentTransform(), false)
+                .setModelViewOffsetRectSnap(0, 0, rect)
+                .setRoundRectClipState(currentSnapshot()->roundRectClipState)
+                .build();
+        DRAW_DOUBLE_STENCIL_IF(!layer->hasDrawnSinceUpdate, renderGlop(glop));
+        return;
+    }
+
+    const float alpha = getLayerAlpha(layer);
+
     setupDraw();
 
     // We must get (and therefore bind) the region mesh buffer
@@ -3110,7 +3161,7 @@
                 Glop glop;
                 GlopBuilder(mRenderState, mCaches, &glop)
                         .setMeshTexturedIndexedQuads(layer->mesh, layer->meshElementCount)
-                        .setFillLayer(layer->getTexture(), layer->getColorFilter(), getLayerAlpha(layer), layer->getMode())
+                        .setFillLayer(layer->getTexture(), layer->getColorFilter(), getLayerAlpha(layer), layer->getMode(), Blend::ModeOrderSwap::NoSwap)
                         .setTransform(currentSnapshot()->getOrthoMatrix(), *currentTransform(), false)
                         .setModelViewOffsetRectSnap(x, y, Rect(0, 0, layer->layer.getWidth(), layer->layer.getHeight()))
                         .setRoundRectClipState(currentSnapshot()->roundRectClipState)
@@ -3596,7 +3647,8 @@
                 mode = SkXfermode::kSrcOver_Mode;
             }
         }
-        mRenderState.blend().enable(mode, swapSrcDst);
+        mRenderState.blend().enable(mode,
+                swapSrcDst ? Blend::ModeOrderSwap::Swap : Blend::ModeOrderSwap::NoSwap);
     } else {
         mRenderState.blend().disable();
     }
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index c6fdd3f..e009451 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -139,7 +139,7 @@
 
 void RenderNode::prepareLayer(TreeInfo& info, uint32_t dirtyMask) {
     LayerType layerType = properties().layerProperties().type();
-    if (CC_UNLIKELY(layerType == kLayerTypeRenderLayer)) {
+    if (CC_UNLIKELY(layerType == LayerType::RenderLayer)) {
         // Damage applied so far needs to affect our parent, but does not require
         // the layer to be updated. So we pop/push here to clear out the current
         // damage and get a clean state for display list or children updates to
@@ -156,7 +156,7 @@
     LayerType layerType = properties().layerProperties().type();
     // If we are not a layer OR we cannot be rendered (eg, view was detached)
     // we need to destroy any Layers we may have had previously
-    if (CC_LIKELY(layerType != kLayerTypeRenderLayer) || CC_UNLIKELY(!isRenderable())) {
+    if (CC_LIKELY(layerType != LayerType::RenderLayer) || CC_UNLIKELY(!isRenderable())) {
         if (CC_UNLIKELY(mLayer)) {
             LayerRenderer::destroyLayer(mLayer);
             mLayer = nullptr;
@@ -384,7 +384,7 @@
             renderer.concatMatrix(*properties().getTransformMatrix());
         }
     }
-    const bool isLayer = properties().layerProperties().type() != kLayerTypeNone;
+    const bool isLayer = properties().layerProperties().type() != LayerType::None;
     int clipFlags = properties().getClippingFlags();
     if (properties().getAlpha() < 1) {
         if (isLayer) {
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
index bb6d087..9f1ceed 100644
--- a/libs/hwui/RenderProperties.cpp
+++ b/libs/hwui/RenderProperties.cpp
@@ -33,14 +33,12 @@
 namespace android {
 namespace uirenderer {
 
-LayerProperties::LayerProperties()
-        : mType(kLayerTypeNone)
-        , mColorFilter(nullptr) {
+LayerProperties::LayerProperties() {
     reset();
 }
 
 LayerProperties::~LayerProperties() {
-    setType(kLayerTypeNone);
+    setType(LayerType::None);
 }
 
 void LayerProperties::reset() {
@@ -146,7 +144,7 @@
         }
     }
 
-    const bool isLayer = layerProperties().type() != kLayerTypeNone;
+    const bool isLayer = layerProperties().type() != LayerType::None;
     int clipFlags = getClippingFlags();
     if (mPrimitiveFields.mAlpha < 1) {
         if (isLayer) {
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index f0e22d6..61e98d2 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -49,12 +49,12 @@
 #define RP_SET_AND_DIRTY(a, b) RP_SET(a, b, mPrimitiveFields.mMatrixOrPivotDirty = true)
 
 // Keep in sync with View.java:LAYER_TYPE_*
-enum LayerType {
-    kLayerTypeNone = 0,
+enum class LayerType {
+    None = 0,
     // Although we cannot build the software layer directly (must be done at
     // record time), this information is used when applying alpha.
-    kLayerTypeSoftware = 1,
-    kLayerTypeRenderLayer = 2,
+    Software = 1,
+    RenderLayer = 2,
     // TODO: LayerTypeSurfaceTexture? Maybe?
 };
 
@@ -124,12 +124,12 @@
 
     friend class RenderProperties;
 
-    LayerType mType;
+    LayerType mType = LayerType::None;
     // Whether or not that Layer's content is opaque, doesn't include alpha
     bool mOpaque;
     uint8_t mAlpha;
     SkXfermode::Mode mMode;
-    SkColorFilter* mColorFilter;
+    SkColorFilter* mColorFilter = nullptr;
 };
 
 /*
diff --git a/libs/hwui/renderstate/Blend.cpp b/libs/hwui/renderstate/Blend.cpp
index 789f6cc..29927ed 100644
--- a/libs/hwui/renderstate/Blend.cpp
+++ b/libs/hwui/renderstate/Blend.cpp
@@ -78,10 +78,10 @@
     // gl blending off by default
 }
 
-void Blend::enable(SkXfermode::Mode mode, bool swapSrcDst) {
+void Blend::enable(SkXfermode::Mode mode, ModeOrderSwap modeUsage) {
     GLenum srcMode;
     GLenum dstMode;
-    getFactors(mode, swapSrcDst, &srcMode, &dstMode);
+    getFactors(mode, modeUsage, &srcMode, &dstMode);
     setFactors(srcMode, dstMode);
 }
 
@@ -105,9 +105,9 @@
     }
 }
 
-void Blend::getFactors(SkXfermode::Mode mode, bool swapSrcDst, GLenum* outSrc, GLenum* outDst) {
-    *outSrc = swapSrcDst ? kBlendsSwap[mode].src : kBlends[mode].src;
-    *outDst = swapSrcDst ? kBlendsSwap[mode].dst : kBlends[mode].dst;
+void Blend::getFactors(SkXfermode::Mode mode, ModeOrderSwap modeUsage, GLenum* outSrc, GLenum* outDst) {
+    *outSrc = (modeUsage == ModeOrderSwap::Swap) ? kBlendsSwap[mode].src : kBlends[mode].src;
+    *outDst = (modeUsage == ModeOrderSwap::Swap) ? kBlendsSwap[mode].dst : kBlends[mode].dst;
 }
 
 void Blend::setFactors(GLenum srcMode, GLenum dstMode) {
diff --git a/libs/hwui/renderstate/Blend.h b/libs/hwui/renderstate/Blend.h
index 6d0c115c..dcc681d 100644
--- a/libs/hwui/renderstate/Blend.h
+++ b/libs/hwui/renderstate/Blend.h
@@ -29,11 +29,18 @@
 class Blend {
     friend class RenderState;
 public:
-    void enable(SkXfermode::Mode mode, bool swapSrcDst);
+    // dictates whether to swap src/dst
+    enum class ModeOrderSwap {
+        NoSwap,
+        Swap,
+    };
+
+    void enable(SkXfermode::Mode mode, ModeOrderSwap modeUsage);
     void disable();
     void syncEnabled();
 
-    static void getFactors(SkXfermode::Mode mode, bool swapSrcDst, GLenum* outSrc, GLenum* outDst);
+    static void getFactors(SkXfermode::Mode mode, ModeOrderSwap modeUsage,
+            GLenum* outSrc, GLenum* outDst);
     void setFactors(GLenum src, GLenum dst);
 
     void dump();
diff --git a/libs/hwui/renderstate/MeshState.cpp b/libs/hwui/renderstate/MeshState.cpp
index 6b00020..0521f65 100644
--- a/libs/hwui/renderstate/MeshState.cpp
+++ b/libs/hwui/renderstate/MeshState.cpp
@@ -37,7 +37,7 @@
 
     mCurrentBuffer = mUnitQuadBuffer;
 
-    std::unique_ptr<uint16_t[]> regionIndices(new uint16_t[kMaxNumberOfQuads * 6]);
+    uint16_t regionIndices[kMaxNumberOfQuads * 6];
     for (uint32_t i = 0; i < kMaxNumberOfQuads; i++) {
         uint16_t quad = i * 4;
         int index = i * 6;
@@ -50,8 +50,7 @@
     }
     glGenBuffers(1, &mQuadListIndices);
     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mQuadListIndices);
-    glBufferData(GL_ELEMENT_ARRAY_BUFFER, kMaxNumberOfQuads * 6 * sizeof(uint16_t),
-            regionIndices.get(), GL_STATIC_DRAW);
+    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(regionIndices), regionIndices, GL_STATIC_DRAW);
     mCurrentIndicesBuffer = mQuadListIndices;
 
     // position attribute always enabled
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/java/android/media/midi/MidiDevice.java b/media/java/android/media/midi/MidiDevice.java
index 1a39485..af0737d 100644
--- a/media/java/android/media/midi/MidiDevice.java
+++ b/media/java/android/media/midi/MidiDevice.java
@@ -24,11 +24,13 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import dalvik.system.CloseGuard;
+
 import java.io.Closeable;
 import java.io.IOException;
 
 /**
- * This class is used for sending and receiving data to and from an MIDI device
+ * This class is used for sending and receiving data to and from a MIDI device
  * Instances of this class are created by {@link MidiManager#openDevice}.
  *
  * CANDIDATE FOR PUBLIC API
@@ -42,11 +44,10 @@
     private Context mContext;
     private ServiceConnection mServiceConnection;
 
+    private final CloseGuard mGuard = CloseGuard.get();
+
     /* package */ MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server) {
-        mDeviceInfo = deviceInfo;
-        mDeviceServer = server;
-        mContext = null;
-        mServiceConnection = null;
+        this(deviceInfo, server, null, null);
     }
 
     /* package */ MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server,
@@ -55,6 +56,7 @@
         mDeviceServer = server;
         mContext = context;
         mServiceConnection = serviceConnection;
+        mGuard.open("close");
     }
 
     /**
@@ -108,10 +110,23 @@
 
     @Override
     public void close() throws IOException {
-        if (mContext != null && mServiceConnection != null) {
-            mContext.unbindService(mServiceConnection);
-            mContext = null;
-            mServiceConnection = null;
+        synchronized (mGuard) {
+            mGuard.close();
+            if (mContext != null && mServiceConnection != null) {
+                mContext.unbindService(mServiceConnection);
+                mContext = null;
+                mServiceConnection = null;
+            }
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            mGuard.warnIfOpen();
+            close();
+        } finally {
+            super.finalize();
         }
     }
 
diff --git a/media/java/android/media/midi/MidiDeviceInfo.java b/media/java/android/media/midi/MidiDeviceInfo.java
index b7756fd..1e97258 100644
--- a/media/java/android/media/midi/MidiDeviceInfo.java
+++ b/media/java/android/media/midi/MidiDeviceInfo.java
@@ -31,7 +31,7 @@
  * CANDIDATE FOR PUBLIC API
  * @hide
  */
-public class MidiDeviceInfo implements Parcelable {
+public final class MidiDeviceInfo implements Parcelable {
 
     private static final String TAG = "MidiDeviceInfo";
 
diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java
index 4d59c63..3b4b6f0 100644
--- a/media/java/android/media/midi/MidiDeviceServer.java
+++ b/media/java/android/media/midi/MidiDeviceServer.java
@@ -24,11 +24,14 @@
 import android.system.OsConstants;
 import android.util.Log;
 
+import dalvik.system.CloseGuard;
+
 import libcore.io.IoUtils;
 
 import java.io.Closeable;
 import java.io.IOException;
 import java.util.HashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * Internal class used for providing an implementation for a MIDI device.
@@ -54,6 +57,12 @@
     // MidiOutputPorts for clients connected to our input ports
     private final MidiOutputPort[] mInputPortOutputPorts;
 
+    // List of all MidiInputPorts we created
+    private final CopyOnWriteArrayList<MidiInputPort> mInputPorts
+            = new CopyOnWriteArrayList<MidiInputPort>();
+
+    private final CloseGuard mGuard = CloseGuard.get();
+
     abstract private class PortClient implements IBinder.DeathRecipient {
         final IBinder mToken;
 
@@ -105,6 +114,7 @@
         void close() {
             mToken.unlinkToDeath(this, 0);
             mOutputPortDispatchers[mInputPort.getPortNumber()].getSender().disconnect(mInputPort);
+            mInputPorts.remove(mInputPort);
             IoUtils.closeQuietly(mInputPort);
         }
     }
@@ -169,6 +179,7 @@
                                                     OsConstants.SOCK_SEQPACKET);
                 MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber);
                 mOutputPortDispatchers[portNumber].getSender().connect(inputPort);
+                mInputPorts.add(inputPort);
                 OutputPortClient client = new OutputPortClient(token, inputPort);
                 synchronized (mPortClients) {
                     mPortClients.put(token, client);
@@ -204,6 +215,8 @@
         for (int i = 0; i < numOutputPorts; i++) {
             mOutputPortDispatchers[i] = new MidiDispatcher();
         }
+
+        mGuard.open("close");
     }
 
     /* package */ IMidiDeviceServer getBinderInterface() {
@@ -219,11 +232,35 @@
 
     @Override
     public void close() throws IOException {
+        synchronized (mGuard) {
+            mGuard.close();
+
+            for (int i = 0; i < mInputPortCount; i++) {
+                MidiOutputPort outputPort = mInputPortOutputPorts[i];
+                if (outputPort != null) {
+                    IoUtils.closeQuietly(outputPort);
+                    mInputPortOutputPorts[i] = null;
+                }
+            }
+            for (MidiInputPort inputPort : mInputPorts) {
+                IoUtils.closeQuietly(inputPort);
+            }
+            mInputPorts.clear();
+            try {
+                mMidiManager.unregisterDeviceServer(mServer);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException in unregisterDeviceServer");
+            }
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
         try {
-            // FIXME - close input and output ports too?
-            mMidiManager.unregisterDeviceServer(mServer);
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException in unregisterDeviceServer");
+            mGuard.warnIfOpen();
+            close();
+        } finally {
+            super.finalize();
         }
     }
 
diff --git a/media/java/android/media/midi/MidiDispatcher.java b/media/java/android/media/midi/MidiDispatcher.java
index b2d8b6f..a5193f1 100644
--- a/media/java/android/media/midi/MidiDispatcher.java
+++ b/media/java/android/media/midi/MidiDispatcher.java
@@ -17,7 +17,7 @@
 package android.media.midi;
 
 import java.io.IOException;
-import java.util.ArrayList;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * Utility class for dispatching MIDI data to a list of {@link MidiReceiver}s.
@@ -29,9 +29,10 @@
  * CANDIDATE FOR PUBLIC API
  * @hide
  */
-public class MidiDispatcher extends MidiReceiver {
+public final class MidiDispatcher extends MidiReceiver {
 
-    private final ArrayList<MidiReceiver> mReceivers = new ArrayList<MidiReceiver>();
+    private final CopyOnWriteArrayList<MidiReceiver> mReceivers
+            = new CopyOnWriteArrayList<MidiReceiver>();
 
     private final MidiSender mSender = new MidiSender() {
         /**
@@ -72,17 +73,12 @@
 
     @Override
     public void receive(byte[] msg, int offset, int count, long timestamp) throws IOException {
-        synchronized (mReceivers) {
-           for (int i = 0; i < mReceivers.size(); ) {
-                MidiReceiver receiver = mReceivers.get(i);
-                try {
-                    receiver.receive(msg, offset, count, timestamp);
-                    i++;    // increment only on success. on failure we remove the receiver
-                            // so i should not be incremented
-                } catch (IOException e) {
-                    // if the receiver fails we remove the receiver but do not propogate the exception
-                    mSender.disconnect(receiver);
-                }
+       for (MidiReceiver receiver : mReceivers) {
+            try {
+                receiver.receive(msg, offset, count, timestamp);
+            } catch (IOException e) {
+                // if the receiver fails we remove the receiver but do not propogate the exception
+                mReceivers.remove(receiver);
             }
         }
     }
diff --git a/media/java/android/media/midi/MidiInputPort.java b/media/java/android/media/midi/MidiInputPort.java
index 5d944cb..8674969 100644
--- a/media/java/android/media/midi/MidiInputPort.java
+++ b/media/java/android/media/midi/MidiInputPort.java
@@ -35,15 +35,16 @@
  * CANDIDATE FOR PUBLIC API
  * @hide
  */
-public class MidiInputPort extends MidiReceiver implements Closeable {
+public final class MidiInputPort extends MidiReceiver implements Closeable {
     private static final String TAG = "MidiInputPort";
 
-    private final IMidiDeviceServer mDeviceServer;
+    private IMidiDeviceServer mDeviceServer;
     private final IBinder mToken;
     private final int mPortNumber;
     private final FileOutputStream mOutputStream;
 
     private final CloseGuard mGuard = CloseGuard.get();
+    private boolean mIsClosed;
 
     // buffer to use for sending data out our output stream
     private final byte[] mBuffer = new byte[MidiPortImpl.MAX_PACKET_SIZE];
@@ -97,23 +98,27 @@
 
     @Override
     public void close() throws IOException {
-        mGuard.close();
-        mOutputStream.close();
-        if (mDeviceServer != null) {
-            try {
-                mDeviceServer.closePort(mToken);
-            } catch (RemoteException e) {
-                Log.e(TAG, "RemoteException in MidiInputPort.close()");
+        synchronized (mGuard) {
+            if (mIsClosed) return;
+            mGuard.close();
+            mOutputStream.close();
+            if (mDeviceServer != null) {
+                try {
+                    mDeviceServer.closePort(mToken);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException in MidiInputPort.close()");
+                }
             }
+            mIsClosed = true;
         }
     }
 
     @Override
     protected void finalize() throws Throwable {
         try {
-            if (mGuard != null) {
-                mGuard.warnIfOpen();
-            }
+            mGuard.warnIfOpen();
+            // not safe to make binder calls from finalize()
+            mDeviceServer = null;
             close();
         } finally {
             super.finalize();
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index 08ac25a..d7b8c57 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -42,7 +42,7 @@
  * CANDIDATE FOR PUBLIC API
  * @hide
  */
-public class MidiManager {
+public final class MidiManager {
     private static final String TAG = "MidiManager";
 
     private final Context mContext;
diff --git a/media/java/android/media/midi/MidiOutputPort.java b/media/java/android/media/midi/MidiOutputPort.java
index d46b202..b31cdd3 100644
--- a/media/java/android/media/midi/MidiOutputPort.java
+++ b/media/java/android/media/midi/MidiOutputPort.java
@@ -35,16 +35,17 @@
  * CANDIDATE FOR PUBLIC API
  * @hide
  */
-public class MidiOutputPort extends MidiSender implements Closeable {
+public final class MidiOutputPort extends MidiSender implements Closeable {
     private static final String TAG = "MidiOutputPort";
 
-    private final IMidiDeviceServer mDeviceServer;
+    private IMidiDeviceServer mDeviceServer;
     private final IBinder mToken;
     private final int mPortNumber;
     private final FileInputStream mInputStream;
     private final MidiDispatcher mDispatcher = new MidiDispatcher();
 
     private final CloseGuard mGuard = CloseGuard.get();
+    private boolean mIsClosed;
 
     // This thread reads MIDI events from a socket and distributes them to the list of
     // MidiReceivers attached to this device.
@@ -113,23 +114,28 @@
 
     @Override
     public void close() throws IOException {
-        mGuard.close();
-        mInputStream.close();
-        if (mDeviceServer != null) {
-            try {
-                mDeviceServer.closePort(mToken);
-            } catch (RemoteException e) {
-                Log.e(TAG, "RemoteException in MidiOutputPort.close()");
+        synchronized (mGuard) {
+            if (mIsClosed) return;
+
+            mGuard.close();
+            mInputStream.close();
+            if (mDeviceServer != null) {
+                try {
+                    mDeviceServer.closePort(mToken);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException in MidiOutputPort.close()");
+                }
             }
+            mIsClosed = true;
         }
     }
 
     @Override
     protected void finalize() throws Throwable {
         try {
-            if (mGuard != null) {
-                mGuard.warnIfOpen();
-            }
+            mGuard.warnIfOpen();
+            // not safe to make binder calls from finalize()
+            mDeviceServer = null;
             close();
         } finally {
             super.finalize();
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/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
index 925d17e..84544ff 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -33,7 +33,7 @@
     private static Method sPropertyMethod;
     static {
         try {
-            Class<?> c = Class.forName("android.view.GLES20Canvas");
+            Class<?> c = Class.forName("android.view.DisplayListCanvas");
             sPropertyMethod = c.getDeclaredMethod("setProperty", String.class, String.class);
             if (!sPropertyMethod.isAccessible()) sPropertyMethod.setAccessible(true);
         } catch (ClassNotFoundException e) {
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/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/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7e69e87..a4f3f23 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -496,7 +496,12 @@
     int mLastDisplayFreezeDuration = 0;
     Object mLastFinishedFreezeSource = null;
     boolean mWaitingForConfig = false;
-    boolean mWindowsFreezingScreen = false;
+
+    final static int WINDOWS_FREEZING_SCREENS_NONE = 0;
+    final static int WINDOWS_FREEZING_SCREENS_ACTIVE = 1;
+    final static int WINDOWS_FREEZING_SCREENS_TIMEOUT = 2;
+    private int mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
+
     boolean mClientFreezingScreen = false;
     int mAppsFreezingScreen = 0;
     int mLastWindowForcedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -4699,7 +4704,7 @@
                 if (mAppsFreezingScreen == 1) {
                     startFreezingDisplayLocked(false, 0, 0);
                     mH.removeMessages(H.APP_FREEZE_TIMEOUT);
-                    mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 5000);
+                    mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 2000);
                 }
             }
             final int N = wtoken.allAppWindows.size();
@@ -6415,7 +6420,7 @@
         mAltOrientation = altOrientation;
         mPolicy.setRotationLw(mRotation);
 
-        mWindowsFreezingScreen = true;
+        mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
         mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
         mH.sendEmptyMessageDelayed(H.WINDOW_FREEZE_TIMEOUT, WINDOW_FREEZE_TIMEOUT_DURATION);
         mWaitingForConfig = true;
@@ -7784,6 +7789,7 @@
                     // TODO(multidisplay): Can non-default displays rotate?
                     synchronized (mWindowMap) {
                         Slog.w(TAG, "Window freeze timeout expired.");
+                        mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
                         final WindowList windows = getDefaultWindowListLocked();
                         int i = windows.size();
                         while (i > 0) {
@@ -7851,6 +7857,7 @@
                 case APP_FREEZE_TIMEOUT: {
                     synchronized (mWindowMap) {
                         Slog.w(TAG, "App freeze timeout expired.");
+                        mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
                         final int numStacks = mStackIdToStack.size();
                         for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
                             final TaskStack stack = mStackIdToStack.valueAt(stackNdx);
@@ -8914,8 +8921,8 @@
             w.mOrientationChanging = true;
             w.mLastFreezeDuration = 0;
             mInnerFields.mOrientationChangeComplete = false;
-            if (!mWindowsFreezingScreen) {
-                mWindowsFreezingScreen = true;
+            if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) {
+                mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
                 // XXX should probably keep timeout from
                 // when we first froze the display.
                 mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
@@ -9905,8 +9912,8 @@
                 "With display frozen, orientationChangeComplete="
                 + mInnerFields.mOrientationChangeComplete);
         if (mInnerFields.mOrientationChangeComplete) {
-            if (mWindowsFreezingScreen) {
-                mWindowsFreezingScreen = false;
+            if (mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
+                mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
                 mLastFinishedFreezeSource = mInnerFields.mLastWindowFreezeSource;
                 mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
             }
@@ -10188,7 +10195,7 @@
         } else {
             mInnerFields.mOrientationChangeComplete = true;
             mInnerFields.mLastWindowFreezeSource = mAnimator.mLastWindowFreezeSource;
-            if (mWindowsFreezingScreen) {
+            if (mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
                 doRequest = true;
             }
         }
@@ -10524,7 +10531,8 @@
             return;
         }
 
-        if (mWaitingForConfig || mAppsFreezingScreen > 0 || mWindowsFreezingScreen
+        if (mWaitingForConfig || mAppsFreezingScreen > 0
+                || mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE
                 || mClientFreezingScreen) {
             if (DEBUG_ORIENTATION) Slog.d(TAG,
                 "stopFreezingDisplayLocked: Returning mWaitingForConfig=" + mWaitingForConfig
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;
     }
 
     /**