Merge "Fix bug 5214224 - Align stock framework list item padding for dialogs"
diff --git a/core/java/android/bluetooth/BluetoothHealth.java b/core/java/android/bluetooth/BluetoothHealth.java
index c165d92..9b2b8ca 100644
--- a/core/java/android/bluetooth/BluetoothHealth.java
+++ b/core/java/android/bluetooth/BluetoothHealth.java
@@ -81,6 +81,20 @@
      */
     public static final int CHANNEL_TYPE_ANY = 12;
 
+    /** @hide */
+    public static final int HEALTH_OPERATION_SUCCESS = 6000;
+    /** @hide */
+    public static final int HEALTH_OPERATION_ERROR = 6001;
+    /** @hide */
+    public static final int HEALTH_OPERATION_INVALID_ARGS = 6002;
+    /** @hide */
+    public static final int HEALTH_OPERATION_GENERIC_FAILURE = 6003;
+    /** @hide */
+    public static final int HEALTH_OPERATION_NOT_FOUND = 6004;
+    /** @hide */
+    public static final int HEALTH_OPERATION_NOT_ALLOWED = 6005;
+
+
     /**
      * Register an application configuration that acts as a Health SINK.
      * This is the configuration that will be used to communicate with health devices
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index e4f2d32..56da69d 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -20,6 +20,7 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHealth;
 import android.bluetooth.BluetoothInputDevice;
 import android.bluetooth.BluetoothPan;
 import android.bluetooth.BluetoothProfile;
@@ -974,6 +975,22 @@
     }
 
     /**
+     * Called by native code for the async response to a Connect
+     * method call to org.bluez.Health
+     *
+     * @param chanCode The internal id of the channel
+     * @param result Result code of the operation.
+     */
+    private void onHealthDeviceConnectionResult(int chanCode, int result) {
+        log ("onHealthDeviceConnectionResult " + chanCode + " " + result);
+        // Success case gets handled by Property Change signal
+        if (result != BluetoothHealth.HEALTH_OPERATION_SUCCESS) {
+            mBluetoothService.onHealthDeviceChannelConnectionError(chanCode,
+                                                 BluetoothHealth.STATE_CHANNEL_DISCONNECTED);
+        }
+    }
+
+    /**
      * Called by native code on a DeviceDisconnected signal from
      * org.bluez.NetworkServer.
      *
diff --git a/core/java/android/server/BluetoothHealthProfileHandler.java b/core/java/android/server/BluetoothHealthProfileHandler.java
index a6ada2b..2d80de4 100644
--- a/core/java/android/server/BluetoothHealthProfileHandler.java
+++ b/core/java/android/server/BluetoothHealthProfileHandler.java
@@ -84,7 +84,6 @@
             result = 31 * result + (mChannelPath == null ? 0 : mChannelPath.hashCode());
             result = 31 * result + mDevice.hashCode();
             result = 31 * result + mConfig.hashCode();
-            result = 31 * result + mState;
             result = 31 * result + mChannelType;
             return result;
         }
@@ -152,7 +151,7 @@
                 String channelType = getStringChannelType(chan.mChannelType);
 
                 if (!mBluetoothService.createChannelNative(deviceObjectPath, configPath,
-                          channelType)) {
+                          channelType, chan.hashCode())) {
                     int prevState = chan.mState;
                     int state = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
                     callHealthChannelCallback(chan.mConfig, chan.mDevice, prevState, state, null,
@@ -258,7 +257,7 @@
 
     boolean disconnectChannel(BluetoothDevice device,
             BluetoothHealthAppConfiguration config, int id) {
-        HealthChannel chan = findChannelById(device, config, id);
+        HealthChannel chan = findChannelById(id);
         if (chan == null) {
           return false;
         }
@@ -273,7 +272,8 @@
         callHealthChannelCallback(config, device, prevState, chan.mState,
                 null, chan.hashCode());
 
-        if (!mBluetoothService.destroyChannelNative(deviceObjectPath, chan.mChannelPath)) {
+        if (!mBluetoothService.destroyChannelNative(deviceObjectPath, chan.mChannelPath,
+                                                    chan.hashCode())) {
             prevState = chan.mState;
             chan.mState = BluetoothHealth.STATE_CHANNEL_CONNECTED;
             callHealthChannelCallback(config, device, prevState, chan.mState,
@@ -284,8 +284,7 @@
         }
     }
 
-    private HealthChannel findChannelById(BluetoothDevice device,
-            BluetoothHealthAppConfiguration config, int id) {
+    private HealthChannel findChannelById(int id) {
         for (HealthChannel chan : mHealthChannels) {
             if (chan.hashCode() == id) return chan;
         }
@@ -384,6 +383,15 @@
         }
     }
 
+    /*package*/ void onHealthDeviceChannelConnectionError(int chanCode,
+                                                          int state) {
+        HealthChannel channel = findChannelById(chanCode);
+        if (channel == null) errorLog("No record of this channel:" + chanCode);
+
+        callHealthChannelCallback(channel.mConfig, channel.mDevice, channel.mState, state, null,
+                chanCode);
+    }
+
     private BluetoothHealthAppConfiguration findHealthApplication(
             BluetoothDevice device, String channelPath) {
         BluetoothHealthAppConfiguration config = null;
@@ -424,9 +432,19 @@
         config = findHealthApplication(device, channelPath);
 
         if (exists) {
+            channel = findConnectingChannel(device, config);
+            if (channel == null) {
+               channel = new HealthChannel(device, config, null, false,
+                       channelPath);
+               channel.mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
+               mHealthChannels.add(channel);
+            }
+            channel.mChannelPath = channelPath;
+
             fd = mBluetoothService.getChannelFdNative(channelPath);
             if (fd == null) {
                 errorLog("Error obtaining fd for channel:" + channelPath);
+                disconnectChannel(device, config, channel.hashCode());
                 return;
             }
             boolean mainChannel =
@@ -440,18 +458,10 @@
                 }
                 if (mainChannelPath.equals(channelPath)) mainChannel = true;
             }
-            channel = findConnectingChannel(device, config);
-            if (channel != null) {
-               channel.mChannelFd = fd;
-               channel.mMainChannel = mainChannel;
-               channel.mChannelPath = channelPath;
-               prevState = channel.mState;
-            } else {
-               channel = new HealthChannel(device, config, fd, mainChannel,
-                       channelPath);
-               mHealthChannels.add(channel);
-               prevState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
-            }
+
+            channel.mChannelFd = fd;
+            channel.mMainChannel = mainChannel;
+            prevState = channel.mState;
             state = BluetoothHealth.STATE_CHANNEL_CONNECTED;
         } else {
             channel = findChannelByPath(device, channelPath);
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index 03180ca..00d3331 100755
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -2255,6 +2255,14 @@
         }
     }
 
+    /*package*/ void onHealthDeviceChannelConnectionError(int channelCode,
+            int newState) {
+        synchronized(mBluetoothHealthProfileHandler) {
+            mBluetoothHealthProfileHandler.onHealthDeviceChannelConnectionError(channelCode,
+                                                                                newState);
+        }
+    }
+
     public int getHealthDeviceConnectionState(BluetoothDevice device) {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
                 "Need BLUETOOTH permission");
@@ -2785,8 +2793,9 @@
             String channelType);
     native String registerHealthApplicationNative(int dataType, String role, String name);
     native boolean unregisterHealthApplicationNative(String path);
-    native boolean createChannelNative(String devicePath, String appPath, String channelType);
-    native boolean destroyChannelNative(String devicePath, String channelpath);
+    native boolean createChannelNative(String devicePath, String appPath, String channelType,
+                                       int code);
+    native boolean destroyChannelNative(String devicePath, String channelpath, int code);
     native String getMainChannelNative(String path);
     native String getChannelApplicationNative(String channelPath);
     native ParcelFileDescriptor getChannelFdNative(String channelPath);
diff --git a/core/java/android/text/style/SuggestionRangeSpan.java b/core/java/android/text/style/SuggestionRangeSpan.java
index a637b1c..2dbfc72 100644
--- a/core/java/android/text/style/SuggestionRangeSpan.java
+++ b/core/java/android/text/style/SuggestionRangeSpan.java
@@ -28,15 +28,11 @@
  * @hide
  */
 public class SuggestionRangeSpan extends CharacterStyle implements ParcelableSpan {
-    private final int mBackgroundColor;
+    private int mBackgroundColor;
 
-    @Override
-    public void updateDrawState(TextPaint tp) {
-        tp.bgColor = mBackgroundColor;
-    }
-
-    public SuggestionRangeSpan(int color) {
-        mBackgroundColor = color;
+    public SuggestionRangeSpan() {
+        // 0 is a fully transparent black. Has to be set using #setBackgroundColor
+        mBackgroundColor = 0;
     }
 
     public SuggestionRangeSpan(Parcel src) {
@@ -57,4 +53,13 @@
     public int getSpanTypeId() {
         return TextUtils.SUGGESTION_RANGE_SPAN;
     }
+
+    public void setBackgroundColor(int backgroundColor) {
+        mBackgroundColor = backgroundColor;
+    }
+
+    @Override
+    public void updateDrawState(TextPaint tp) {
+        tp.bgColor = mBackgroundColor;
+    }
 }
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index 693a7a9..fecf0f7 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -264,4 +264,14 @@
             tp.setUnderlineText(mEasyCorrectUnderlineColor, mEasyCorrectUnderlineThickness);
         }
     }
+
+    /**
+     * @return The color of the underline for that span, or 0 if there is no underline
+     */
+    public int getUnderlineColor() {
+        // The order here should match what is used in updateDrawState
+        if ((mFlags & FLAG_MISSPELLED) != 0) return mMisspelledUnderlineColor;
+        if ((mFlags & FLAG_EASY_CORRECT) != 0) return mEasyCorrectUnderlineColor;
+        return 0;
+    }
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 4a9c5bd..7a35015 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -27,6 +27,7 @@
 import android.graphics.Interpolator;
 import android.graphics.LinearGradient;
 import android.graphics.Matrix;
+import android.graphics.Matrix.ScaleToFit;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
@@ -2001,64 +2002,133 @@
     @ViewDebug.ExportedProperty
     int mViewFlags;
 
-    /**
-     * The transform matrix for the View. This transform is calculated internally
-     * based on the rotation, scaleX, and scaleY properties. The identity matrix
-     * is used by default. Do *not* use this variable directly; instead call
-     * getMatrix(), which will automatically recalculate the matrix if necessary
-     * to get the correct matrix based on the latest rotation and scale properties.
-     */
-    private final Matrix mMatrix = new Matrix();
+    static class TransformationInfo {
+        /**
+         * The transform matrix for the View. This transform is calculated internally
+         * based on the rotation, scaleX, and scaleY properties. The identity matrix
+         * is used by default. Do *not* use this variable directly; instead call
+         * getMatrix(), which will automatically recalculate the matrix if necessary
+         * to get the correct matrix based on the latest rotation and scale properties.
+         */
+        private final Matrix mMatrix = new Matrix();
 
-    /**
-     * The transform matrix for the View. This transform is calculated internally
-     * based on the rotation, scaleX, and scaleY properties. The identity matrix
-     * is used by default. Do *not* use this variable directly; instead call
-     * getInverseMatrix(), which will automatically recalculate the matrix if necessary
-     * to get the correct matrix based on the latest rotation and scale properties.
-     */
-    private Matrix mInverseMatrix;
+        /**
+         * The transform matrix for the View. This transform is calculated internally
+         * based on the rotation, scaleX, and scaleY properties. The identity matrix
+         * is used by default. Do *not* use this variable directly; instead call
+         * getInverseMatrix(), which will automatically recalculate the matrix if necessary
+         * to get the correct matrix based on the latest rotation and scale properties.
+         */
+        private Matrix mInverseMatrix;
 
-    /**
-     * An internal variable that tracks whether we need to recalculate the
-     * transform matrix, based on whether the rotation or scaleX/Y properties
-     * have changed since the matrix was last calculated.
-     */
-    boolean mMatrixDirty = false;
+        /**
+         * An internal variable that tracks whether we need to recalculate the
+         * transform matrix, based on whether the rotation or scaleX/Y properties
+         * have changed since the matrix was last calculated.
+         */
+        boolean mMatrixDirty = false;
 
-    /**
-     * An internal variable that tracks whether we need to recalculate the
-     * transform matrix, based on whether the rotation or scaleX/Y properties
-     * have changed since the matrix was last calculated.
-     */
-    private boolean mInverseMatrixDirty = true;
+        /**
+         * An internal variable that tracks whether we need to recalculate the
+         * transform matrix, based on whether the rotation or scaleX/Y properties
+         * have changed since the matrix was last calculated.
+         */
+        private boolean mInverseMatrixDirty = true;
 
-    /**
-     * A variable that tracks whether we need to recalculate the
-     * transform matrix, based on whether the rotation or scaleX/Y properties
-     * have changed since the matrix was last calculated. This variable
-     * is only valid after a call to updateMatrix() or to a function that
-     * calls it such as getMatrix(), hasIdentityMatrix() and getInverseMatrix().
-     */
-    private boolean mMatrixIsIdentity = true;
+        /**
+         * A variable that tracks whether we need to recalculate the
+         * transform matrix, based on whether the rotation or scaleX/Y properties
+         * have changed since the matrix was last calculated. This variable
+         * is only valid after a call to updateMatrix() or to a function that
+         * calls it such as getMatrix(), hasIdentityMatrix() and getInverseMatrix().
+         */
+        private boolean mMatrixIsIdentity = true;
 
-    /**
-     * The Camera object is used to compute a 3D matrix when rotationX or rotationY are set.
-     */
-    private Camera mCamera = null;
+        /**
+         * The Camera object is used to compute a 3D matrix when rotationX or rotationY are set.
+         */
+        private Camera mCamera = null;
 
-    /**
-     * This matrix is used when computing the matrix for 3D rotations.
-     */
-    private Matrix matrix3D = null;
+        /**
+         * This matrix is used when computing the matrix for 3D rotations.
+         */
+        private Matrix matrix3D = null;
 
-    /**
-     * These prev values are used to recalculate a centered pivot point when necessary. The
-     * pivot point is only used in matrix operations (when rotation, scale, or translation are
-     * set), so thes values are only used then as well.
-     */
-    private int mPrevWidth = -1;
-    private int mPrevHeight = -1;
+        /**
+         * These prev values are used to recalculate a centered pivot point when necessary. The
+         * pivot point is only used in matrix operations (when rotation, scale, or translation are
+         * set), so thes values are only used then as well.
+         */
+        private int mPrevWidth = -1;
+        private int mPrevHeight = -1;
+        
+        /**
+         * The degrees rotation around the vertical axis through the pivot point.
+         */
+        @ViewDebug.ExportedProperty
+        float mRotationY = 0f;
+
+        /**
+         * The degrees rotation around the horizontal axis through the pivot point.
+         */
+        @ViewDebug.ExportedProperty
+        float mRotationX = 0f;
+
+        /**
+         * The degrees rotation around the pivot point.
+         */
+        @ViewDebug.ExportedProperty
+        float mRotation = 0f;
+
+        /**
+         * The amount of translation of the object away from its left property (post-layout).
+         */
+        @ViewDebug.ExportedProperty
+        float mTranslationX = 0f;
+
+        /**
+         * The amount of translation of the object away from its top property (post-layout).
+         */
+        @ViewDebug.ExportedProperty
+        float mTranslationY = 0f;
+
+        /**
+         * The amount of scale in the x direction around the pivot point. A
+         * value of 1 means no scaling is applied.
+         */
+        @ViewDebug.ExportedProperty
+        float mScaleX = 1f;
+
+        /**
+         * The amount of scale in the y direction around the pivot point. A
+         * value of 1 means no scaling is applied.
+         */
+        @ViewDebug.ExportedProperty
+        float mScaleY = 1f;
+
+        /**
+         * The amount of scale in the x direction around the pivot point. A
+         * value of 1 means no scaling is applied.
+         */
+        @ViewDebug.ExportedProperty
+        float mPivotX = 0f;
+
+        /**
+         * The amount of scale in the y direction around the pivot point. A
+         * value of 1 means no scaling is applied.
+         */
+        @ViewDebug.ExportedProperty
+        float mPivotY = 0f;
+
+        /**
+         * The opacity of the View. This is a value from 0 to 1, where 0 means
+         * completely transparent and 1 means completely opaque.
+         */
+        @ViewDebug.ExportedProperty
+        float mAlpha = 1f;
+    }
+
+    TransformationInfo mTransformationInfo;
 
     private boolean mLastIsOpaque;
 
@@ -2069,71 +2139,6 @@
     private static final float NONZERO_EPSILON = .001f;
 
     /**
-     * The degrees rotation around the vertical axis through the pivot point.
-     */
-    @ViewDebug.ExportedProperty
-    float mRotationY = 0f;
-
-    /**
-     * The degrees rotation around the horizontal axis through the pivot point.
-     */
-    @ViewDebug.ExportedProperty
-    float mRotationX = 0f;
-
-    /**
-     * The degrees rotation around the pivot point.
-     */
-    @ViewDebug.ExportedProperty
-    float mRotation = 0f;
-
-    /**
-     * The amount of translation of the object away from its left property (post-layout).
-     */
-    @ViewDebug.ExportedProperty
-    float mTranslationX = 0f;
-
-    /**
-     * The amount of translation of the object away from its top property (post-layout).
-     */
-    @ViewDebug.ExportedProperty
-    float mTranslationY = 0f;
-
-    /**
-     * The amount of scale in the x direction around the pivot point. A
-     * value of 1 means no scaling is applied.
-     */
-    @ViewDebug.ExportedProperty
-    float mScaleX = 1f;
-
-    /**
-     * The amount of scale in the y direction around the pivot point. A
-     * value of 1 means no scaling is applied.
-     */
-    @ViewDebug.ExportedProperty
-    float mScaleY = 1f;
-
-    /**
-     * The amount of scale in the x direction around the pivot point. A
-     * value of 1 means no scaling is applied.
-     */
-    @ViewDebug.ExportedProperty
-    float mPivotX = 0f;
-
-    /**
-     * The amount of scale in the y direction around the pivot point. A
-     * value of 1 means no scaling is applied.
-     */
-    @ViewDebug.ExportedProperty
-    float mPivotY = 0f;
-
-    /**
-     * The opacity of the View. This is a value from 0 to 1, where 0 means
-     * completely transparent and 1 means completely opaque.
-     */
-    @ViewDebug.ExportedProperty
-    float mAlpha = 1f;
-
-    /**
      * The distance in pixels from the left edge of this view's parent
      * to the left edge of this view.
      * {@hide}
@@ -6776,8 +6781,11 @@
      * @return The current transform matrix for the view
      */
     public Matrix getMatrix() {
-        updateMatrix();
-        return mMatrix;
+        if (mTransformationInfo != null) {
+            updateMatrix();
+            return mTransformationInfo.mMatrix;
+        }
+        return Matrix.IDENTITY_MATRIX;
     }
 
     /**
@@ -6797,49 +6805,63 @@
      * @return True if the transform matrix is the identity matrix, false otherwise.
      */
     final boolean hasIdentityMatrix() {
-        updateMatrix();
-        return mMatrixIsIdentity;
+        if (mTransformationInfo != null) {
+            updateMatrix();
+            return mTransformationInfo.mMatrixIsIdentity;
+        }
+        return true;
+    }
+
+    void ensureTransformationInfo() {
+        if (mTransformationInfo == null) {
+            mTransformationInfo = new TransformationInfo();
+        }
     }
 
     /**
      * Recomputes the transform matrix if necessary.
      */
     private void updateMatrix() {
-        if (mMatrixDirty) {
+        final TransformationInfo info = mTransformationInfo;
+        if (info == null) {
+            return;
+        }
+        if (info.mMatrixDirty) {
             // transform-related properties have changed since the last time someone
             // asked for the matrix; recalculate it with the current values
 
             // Figure out if we need to update the pivot point
             if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) {
-                if ((mRight - mLeft) != mPrevWidth || (mBottom - mTop) != mPrevHeight) {
-                    mPrevWidth = mRight - mLeft;
-                    mPrevHeight = mBottom - mTop;
-                    mPivotX = mPrevWidth / 2f;
-                    mPivotY = mPrevHeight / 2f;
+                if ((mRight - mLeft) != info.mPrevWidth || (mBottom - mTop) != info.mPrevHeight) {
+                    info.mPrevWidth = mRight - mLeft;
+                    info.mPrevHeight = mBottom - mTop;
+                    info.mPivotX = info.mPrevWidth / 2f;
+                    info.mPivotY = info.mPrevHeight / 2f;
                 }
             }
-            mMatrix.reset();
-            if (!nonzero(mRotationX) && !nonzero(mRotationY)) {
-                mMatrix.setTranslate(mTranslationX, mTranslationY);
-                mMatrix.preRotate(mRotation, mPivotX, mPivotY);
-                mMatrix.preScale(mScaleX, mScaleY, mPivotX, mPivotY);
+            info.mMatrix.reset();
+            if (!nonzero(info.mRotationX) && !nonzero(info.mRotationY)) {
+                info.mMatrix.setTranslate(info.mTranslationX, info.mTranslationY);
+                info.mMatrix.preRotate(info.mRotation, info.mPivotX, info.mPivotY);
+                info.mMatrix.preScale(info.mScaleX, info.mScaleY, info.mPivotX, info.mPivotY);
             } else {
-                if (mCamera == null) {
-                    mCamera = new Camera();
-                    matrix3D = new Matrix();
+                if (info.mCamera == null) {
+                    info.mCamera = new Camera();
+                    info.matrix3D = new Matrix();
                 }
-                mCamera.save();
-                mMatrix.preScale(mScaleX, mScaleY, mPivotX, mPivotY);
-                mCamera.rotate(mRotationX, mRotationY, -mRotation);
-                mCamera.getMatrix(matrix3D);
-                matrix3D.preTranslate(-mPivotX, -mPivotY);
-                matrix3D.postTranslate(mPivotX + mTranslationX, mPivotY + mTranslationY);
-                mMatrix.postConcat(matrix3D);
-                mCamera.restore();
+                info.mCamera.save();
+                info.mMatrix.preScale(info.mScaleX, info.mScaleY, info.mPivotX, info.mPivotY);
+                info.mCamera.rotate(info.mRotationX, info.mRotationY, -info.mRotation);
+                info.mCamera.getMatrix(info.matrix3D);
+                info.matrix3D.preTranslate(-info.mPivotX, -info.mPivotY);
+                info.matrix3D.postTranslate(info.mPivotX + info.mTranslationX,
+                        info.mPivotY + info.mTranslationY);
+                info.mMatrix.postConcat(info.matrix3D);
+                info.mCamera.restore();
             }
-            mMatrixDirty = false;
-            mMatrixIsIdentity = mMatrix.isIdentity();
-            mInverseMatrixDirty = true;
+            info.mMatrixDirty = false;
+            info.mMatrixIsIdentity = info.mMatrix.isIdentity();
+            info.mInverseMatrixDirty = true;
         }
     }
 
@@ -6851,15 +6873,19 @@
      * @return The inverse of the current matrix of this view.
      */
     final Matrix getInverseMatrix() {
-        updateMatrix();
-        if (mInverseMatrixDirty) {
-            if (mInverseMatrix == null) {
-                mInverseMatrix = new Matrix();
+        final TransformationInfo info = mTransformationInfo;
+        if (info != null) {
+            updateMatrix();
+            if (info.mInverseMatrixDirty) {
+                if (info.mInverseMatrix == null) {
+                    info.mInverseMatrix = new Matrix();
+                }
+                info.mMatrix.invert(info.mInverseMatrix);
+                info.mInverseMatrixDirty = false;
             }
-            mMatrix.invert(mInverseMatrix);
-            mInverseMatrixDirty = false;
+            return info.mInverseMatrix;
         }
-        return mInverseMatrix;
+        return Matrix.IDENTITY_MATRIX;
     }
 
     /**
@@ -6905,14 +6931,16 @@
         invalidateParentCaches();
         invalidate(false);
 
+        ensureTransformationInfo();
         final float dpi = mResources.getDisplayMetrics().densityDpi;
-        if (mCamera == null) {
-            mCamera = new Camera();
-            matrix3D = new Matrix();
+        final TransformationInfo info = mTransformationInfo;
+        if (info.mCamera == null) {
+            info.mCamera = new Camera();
+            info.matrix3D = new Matrix();
         }
 
-        mCamera.setLocation(0.0f, 0.0f, -Math.abs(distance) / dpi);
-        mMatrixDirty = true;
+        info.mCamera.setLocation(0.0f, 0.0f, -Math.abs(distance) / dpi);
+        info.mMatrixDirty = true;
 
         invalidate(false);
     }
@@ -6927,7 +6955,7 @@
      * @return The degrees of rotation.
      */
     public float getRotation() {
-        return mRotation;
+        return mTransformationInfo != null ? mTransformationInfo.mRotation : 0;
     }
 
     /**
@@ -6945,12 +6973,14 @@
      * @attr ref android.R.styleable#View_rotation
      */
     public void setRotation(float rotation) {
-        if (mRotation != rotation) {
+        ensureTransformationInfo();
+        final TransformationInfo info = mTransformationInfo;
+        if (info.mRotation != rotation) {
             invalidateParentCaches();
             // Double-invalidation is necessary to capture view's old and new areas
             invalidate(false);
-            mRotation = rotation;
-            mMatrixDirty = true;
+            info.mRotation = rotation;
+            info.mMatrixDirty = true;
             mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
             invalidate(false);
         }
@@ -6966,7 +6996,7 @@
      * @return The degrees of Y rotation.
      */
     public float getRotationY() {
-        return mRotationY;
+        return mTransformationInfo != null ? mTransformationInfo.mRotationY : 0;
     }
 
     /**
@@ -6989,12 +7019,14 @@
      * @attr ref android.R.styleable#View_rotationY
      */
     public void setRotationY(float rotationY) {
-        if (mRotationY != rotationY) {
+        ensureTransformationInfo();
+        final TransformationInfo info = mTransformationInfo;
+        if (info.mRotationY != rotationY) {
             invalidateParentCaches();
             // Double-invalidation is necessary to capture view's old and new areas
             invalidate(false);
-            mRotationY = rotationY;
-            mMatrixDirty = true;
+            info.mRotationY = rotationY;
+            info.mMatrixDirty = true;
             mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
             invalidate(false);
         }
@@ -7010,7 +7042,7 @@
      * @return The degrees of X rotation.
      */
     public float getRotationX() {
-        return mRotationX;
+        return mTransformationInfo != null ? mTransformationInfo.mRotationX : 0;
     }
 
     /**
@@ -7033,12 +7065,14 @@
      * @attr ref android.R.styleable#View_rotationX
      */
     public void setRotationX(float rotationX) {
-        if (mRotationX != rotationX) {
+        ensureTransformationInfo();
+        final TransformationInfo info = mTransformationInfo;
+        if (info.mRotationX != rotationX) {
             invalidateParentCaches();
             // Double-invalidation is necessary to capture view's old and new areas
             invalidate(false);
-            mRotationX = rotationX;
-            mMatrixDirty = true;
+            info.mRotationX = rotationX;
+            info.mMatrixDirty = true;
             mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
             invalidate(false);
         }
@@ -7055,7 +7089,7 @@
      * @return The scaling factor.
      */
     public float getScaleX() {
-        return mScaleX;
+        return mTransformationInfo != null ? mTransformationInfo.mScaleX : 1;
     }
 
     /**
@@ -7069,12 +7103,14 @@
      * @attr ref android.R.styleable#View_scaleX
      */
     public void setScaleX(float scaleX) {
-        if (mScaleX != scaleX) {
+        ensureTransformationInfo();
+        final TransformationInfo info = mTransformationInfo;
+        if (info.mScaleX != scaleX) {
             invalidateParentCaches();
             // Double-invalidation is necessary to capture view's old and new areas
             invalidate(false);
-            mScaleX = scaleX;
-            mMatrixDirty = true;
+            info.mScaleX = scaleX;
+            info.mMatrixDirty = true;
             mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
             invalidate(false);
         }
@@ -7091,7 +7127,7 @@
      * @return The scaling factor.
      */
     public float getScaleY() {
-        return mScaleY;
+        return mTransformationInfo != null ? mTransformationInfo.mScaleY : 1;
     }
 
     /**
@@ -7105,12 +7141,14 @@
      * @attr ref android.R.styleable#View_scaleY
      */
     public void setScaleY(float scaleY) {
-        if (mScaleY != scaleY) {
+        ensureTransformationInfo();
+        final TransformationInfo info = mTransformationInfo;
+        if (info.mScaleY != scaleY) {
             invalidateParentCaches();
             // Double-invalidation is necessary to capture view's old and new areas
             invalidate(false);
-            mScaleY = scaleY;
-            mMatrixDirty = true;
+            info.mScaleY = scaleY;
+            info.mMatrixDirty = true;
             mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
             invalidate(false);
         }
@@ -7127,7 +7165,7 @@
      * @return The x location of the pivot point.
      */
     public float getPivotX() {
-        return mPivotX;
+        return mTransformationInfo != null ? mTransformationInfo.mPivotX : 0;
     }
 
     /**
@@ -7146,13 +7184,15 @@
      * @attr ref android.R.styleable#View_transformPivotX
      */
     public void setPivotX(float pivotX) {
+        ensureTransformationInfo();
         mPrivateFlags |= PIVOT_EXPLICITLY_SET;
-        if (mPivotX != pivotX) {
+        final TransformationInfo info = mTransformationInfo;
+        if (info.mPivotX != pivotX) {
             invalidateParentCaches();
             // Double-invalidation is necessary to capture view's old and new areas
             invalidate(false);
-            mPivotX = pivotX;
-            mMatrixDirty = true;
+            info.mPivotX = pivotX;
+            info.mMatrixDirty = true;
             mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
             invalidate(false);
         }
@@ -7169,7 +7209,7 @@
      * @return The y location of the pivot point.
      */
     public float getPivotY() {
-        return mPivotY;
+        return mTransformationInfo != null ? mTransformationInfo.mPivotY : 0;
     }
 
     /**
@@ -7187,13 +7227,15 @@
      * @attr ref android.R.styleable#View_transformPivotY
      */
     public void setPivotY(float pivotY) {
+        ensureTransformationInfo();
         mPrivateFlags |= PIVOT_EXPLICITLY_SET;
-        if (mPivotY != pivotY) {
+        final TransformationInfo info = mTransformationInfo;
+        if (info.mPivotY != pivotY) {
             invalidateParentCaches();
             // Double-invalidation is necessary to capture view's old and new areas
             invalidate(false);
-            mPivotY = pivotY;
-            mMatrixDirty = true;
+            info.mPivotY = pivotY;
+            info.mMatrixDirty = true;
             mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
             invalidate(false);
         }
@@ -7207,7 +7249,7 @@
      * @return The opacity of the view.
      */
     public float getAlpha() {
-        return mAlpha;
+        return mTransformationInfo != null ? mTransformationInfo.mAlpha : 1;
     }
 
     /**
@@ -7226,7 +7268,8 @@
      * @attr ref android.R.styleable#View_alpha
      */
     public void setAlpha(float alpha) {
-        mAlpha = alpha;
+        ensureTransformationInfo();
+        mTransformationInfo.mAlpha = alpha;
         invalidateParentCaches();
         if (onSetAlpha((int) (alpha * 255))) {
             mPrivateFlags |= ALPHA_SET;
@@ -7248,7 +7291,8 @@
      * @return true if the View subclass handles alpha (the return value for onSetAlpha())
      */
     boolean setAlphaNoInvalidation(float alpha) {
-        mAlpha = alpha;
+        ensureTransformationInfo();
+        mTransformationInfo.mAlpha = alpha;
         boolean subclassHandlesAlpha = onSetAlpha((int) (alpha * 255));
         if (subclassHandlesAlpha) {
             mPrivateFlags |= ALPHA_SET;
@@ -7278,7 +7322,9 @@
     public final void setTop(int top) {
         if (top != mTop) {
             updateMatrix();
-            if (mMatrixIsIdentity) {
+            final boolean matrixIsIdentity = mTransformationInfo == null
+                    || mTransformationInfo.mMatrixIsIdentity;
+            if (matrixIsIdentity) {
                 if (mAttachInfo != null) {
                     int minTop;
                     int yLoc;
@@ -7303,10 +7349,10 @@
 
             onSizeChanged(width, mBottom - mTop, width, oldHeight);
 
-            if (!mMatrixIsIdentity) {
+            if (!matrixIsIdentity) {
                 if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) {
                     // A change in dimension means an auto-centered pivot point changes, too
-                    mMatrixDirty = true;
+                    mTransformationInfo.mMatrixDirty = true;
                 }
                 mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
                 invalidate(true);
@@ -7345,7 +7391,9 @@
     public final void setBottom(int bottom) {
         if (bottom != mBottom) {
             updateMatrix();
-            if (mMatrixIsIdentity) {
+            final boolean matrixIsIdentity = mTransformationInfo == null
+                    || mTransformationInfo.mMatrixIsIdentity;
+            if (matrixIsIdentity) {
                 if (mAttachInfo != null) {
                     int maxBottom;
                     if (bottom < mBottom) {
@@ -7367,10 +7415,10 @@
 
             onSizeChanged(width, mBottom - mTop, width, oldHeight);
 
-            if (!mMatrixIsIdentity) {
+            if (!matrixIsIdentity) {
                 if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) {
                     // A change in dimension means an auto-centered pivot point changes, too
-                    mMatrixDirty = true;
+                    mTransformationInfo.mMatrixDirty = true;
                 }
                 mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
                 invalidate(true);
@@ -7400,7 +7448,9 @@
     public final void setLeft(int left) {
         if (left != mLeft) {
             updateMatrix();
-            if (mMatrixIsIdentity) {
+            final boolean matrixIsIdentity = mTransformationInfo == null
+                    || mTransformationInfo.mMatrixIsIdentity;
+            if (matrixIsIdentity) {
                 if (mAttachInfo != null) {
                     int minLeft;
                     int xLoc;
@@ -7425,10 +7475,10 @@
 
             onSizeChanged(mRight - mLeft, height, oldWidth, height);
 
-            if (!mMatrixIsIdentity) {
+            if (!matrixIsIdentity) {
                 if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) {
                     // A change in dimension means an auto-centered pivot point changes, too
-                    mMatrixDirty = true;
+                    mTransformationInfo.mMatrixDirty = true;
                 }
                 mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
                 invalidate(true);
@@ -7458,7 +7508,9 @@
     public final void setRight(int right) {
         if (right != mRight) {
             updateMatrix();
-            if (mMatrixIsIdentity) {
+            final boolean matrixIsIdentity = mTransformationInfo == null
+                    || mTransformationInfo.mMatrixIsIdentity;
+            if (matrixIsIdentity) {
                 if (mAttachInfo != null) {
                     int maxRight;
                     if (right < mRight) {
@@ -7480,10 +7532,10 @@
 
             onSizeChanged(mRight - mLeft, height, oldWidth, height);
 
-            if (!mMatrixIsIdentity) {
+            if (!matrixIsIdentity) {
                 if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) {
                     // A change in dimension means an auto-centered pivot point changes, too
-                    mMatrixDirty = true;
+                    mTransformationInfo.mMatrixDirty = true;
                 }
                 mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
                 invalidate(true);
@@ -7501,7 +7553,7 @@
      * @return The visual x position of this view, in pixels.
      */
     public float getX() {
-        return mLeft + mTranslationX;
+        return mLeft + (mTransformationInfo != null ? mTransformationInfo.mTranslationX : 0);
     }
 
     /**
@@ -7523,7 +7575,7 @@
      * @return The visual y position of this view, in pixels.
      */
     public float getY() {
-        return mTop + mTranslationY;
+        return mTop + (mTransformationInfo != null ? mTransformationInfo.mTranslationY : 0);
     }
 
     /**
@@ -7546,7 +7598,7 @@
      * @return The horizontal position of this view relative to its left position, in pixels.
      */
     public float getTranslationX() {
-        return mTranslationX;
+        return mTransformationInfo != null ? mTransformationInfo.mTranslationX : 0;
     }
 
     /**
@@ -7560,12 +7612,14 @@
      * @attr ref android.R.styleable#View_translationX
      */
     public void setTranslationX(float translationX) {
-        if (mTranslationX != translationX) {
+        ensureTransformationInfo();
+        final TransformationInfo info = mTransformationInfo;
+        if (info.mTranslationX != translationX) {
             invalidateParentCaches();
             // Double-invalidation is necessary to capture view's old and new areas
             invalidate(false);
-            mTranslationX = translationX;
-            mMatrixDirty = true;
+            info.mTranslationX = translationX;
+            info.mMatrixDirty = true;
             mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
             invalidate(false);
         }
@@ -7580,7 +7634,7 @@
      * in pixels.
      */
     public float getTranslationY() {
-        return mTranslationY;
+        return mTransformationInfo != null ? mTransformationInfo.mTranslationY : 0;
     }
 
     /**
@@ -7594,12 +7648,14 @@
      * @attr ref android.R.styleable#View_translationY
      */
     public void setTranslationY(float translationY) {
-        if (mTranslationY != translationY) {
+        ensureTransformationInfo();
+        final TransformationInfo info = mTransformationInfo;
+        if (info.mTranslationY != translationY) {
             invalidateParentCaches();
             // Double-invalidation is necessary to capture view's old and new areas
             invalidate(false);
-            mTranslationY = translationY;
-            mMatrixDirty = true;
+            info.mTranslationY = translationY;
+            info.mMatrixDirty = true;
             mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
             invalidate(false);
         }
@@ -7609,63 +7665,78 @@
      * @hide
      */
     public void setFastTranslationX(float x) {
-        mTranslationX = x;
-        mMatrixDirty = true;
+        ensureTransformationInfo();
+        final TransformationInfo info = mTransformationInfo;
+        info.mTranslationX = x;
+        info.mMatrixDirty = true;
     }
 
     /**
      * @hide
      */
     public void setFastTranslationY(float y) {
-        mTranslationY = y;
-        mMatrixDirty = true;
+        ensureTransformationInfo();
+        final TransformationInfo info = mTransformationInfo;
+        info.mTranslationY = y;
+        info.mMatrixDirty = true;
     }
 
     /**
      * @hide
      */
     public void setFastX(float x) {
-        mTranslationX = x - mLeft;
-        mMatrixDirty = true;
+        ensureTransformationInfo();
+        final TransformationInfo info = mTransformationInfo;
+        info.mTranslationX = x - mLeft;
+        info.mMatrixDirty = true;
     }
 
     /**
      * @hide
      */
     public void setFastY(float y) {
-        mTranslationY = y - mTop;
-        mMatrixDirty = true;
+        ensureTransformationInfo();
+        final TransformationInfo info = mTransformationInfo;
+        info.mTranslationY = y - mTop;
+        info.mMatrixDirty = true;
     }
 
     /**
      * @hide
      */
     public void setFastScaleX(float x) {
-        mScaleX = x;
-        mMatrixDirty = true;
+        ensureTransformationInfo();
+        final TransformationInfo info = mTransformationInfo;
+        info.mScaleX = x;
+        info.mMatrixDirty = true;
     }
 
     /**
      * @hide
      */
     public void setFastScaleY(float y) {
-        mScaleY = y;
-        mMatrixDirty = true;
+        ensureTransformationInfo();
+        final TransformationInfo info = mTransformationInfo;
+        info.mScaleY = y;
+        info.mMatrixDirty = true;
     }
 
     /**
      * @hide
      */
     public void setFastAlpha(float alpha) {
-        mAlpha = alpha;
+        ensureTransformationInfo();
+        mTransformationInfo.mAlpha = alpha;
     }
 
     /**
      * @hide
      */
     public void setFastRotationY(float y) {
-        mRotationY = y;
-        mMatrixDirty = true;
+        ensureTransformationInfo();
+        final TransformationInfo info = mTransformationInfo;
+        info.mRotationY = y;
+        info.mMatrixDirty = true;
     }
 
     /**
@@ -7675,12 +7746,14 @@
      */
     public void getHitRect(Rect outRect) {
         updateMatrix();
-        if (mMatrixIsIdentity || mAttachInfo == null) {
+        final TransformationInfo info = mTransformationInfo;
+        if (info == null || info.mMatrixIsIdentity || mAttachInfo == null) {
             outRect.set(mLeft, mTop, mRight, mBottom);
         } else {
             final RectF tmpRect = mAttachInfo.mTmpTransformRect;
-            tmpRect.set(-mPivotX, -mPivotY, getWidth() - mPivotX, getHeight() - mPivotY);
-            mMatrix.mapRect(tmpRect);
+            tmpRect.set(-info.mPivotX, -info.mPivotY,
+                    getWidth() - info.mPivotX, getHeight() - info.mPivotY);
+            info.mMatrix.mapRect(tmpRect);
             outRect.set((int) tmpRect.left + mLeft, (int) tmpRect.top + mTop,
                     (int) tmpRect.right + mLeft, (int) tmpRect.bottom + mTop);
         }
@@ -7768,7 +7841,9 @@
     public void offsetTopAndBottom(int offset) {
         if (offset != 0) {
             updateMatrix();
-            if (mMatrixIsIdentity) {
+            final boolean matrixIsIdentity = mTransformationInfo == null
+                    || mTransformationInfo.mMatrixIsIdentity;
+            if (matrixIsIdentity) {
                 final ViewParent p = mParent;
                 if (p != null && mAttachInfo != null) {
                     final Rect r = mAttachInfo.mTmpInvalRect;
@@ -7794,7 +7869,7 @@
             mTop += offset;
             mBottom += offset;
 
-            if (!mMatrixIsIdentity) {
+            if (!matrixIsIdentity) {
                 mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
                 invalidate(false);
             }
@@ -7810,7 +7885,9 @@
     public void offsetLeftAndRight(int offset) {
         if (offset != 0) {
             updateMatrix();
-            if (mMatrixIsIdentity) {
+            final boolean matrixIsIdentity = mTransformationInfo == null
+                    || mTransformationInfo.mMatrixIsIdentity;
+            if (matrixIsIdentity) {
                 final ViewParent p = mParent;
                 if (p != null && mAttachInfo != null) {
                     final Rect r = mAttachInfo.mTmpInvalRect;
@@ -7833,7 +7910,7 @@
             mLeft += offset;
             mRight += offset;
 
-            if (!mMatrixIsIdentity) {
+            if (!matrixIsIdentity) {
                 mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
                 invalidate(false);
             }
@@ -8309,7 +8386,8 @@
     @ViewDebug.ExportedProperty(category = "drawing")
     public boolean isOpaque() {
         return (mPrivateFlags & OPAQUE_MASK) == OPAQUE_MASK &&
-                (mAlpha >= 1.0f - ViewConfiguration.ALPHA_THRESHOLD);
+                ((mTransformationInfo != null ? mTransformationInfo.mAlpha : 1)
+                        >= 1.0f - ViewConfiguration.ALPHA_THRESHOLD);
     }
 
     /**
@@ -10954,7 +11032,9 @@
             if (sizeChanged) {
                 if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) {
                     // A change in dimension means an auto-centered pivot point changes, too
-                    mMatrixDirty = true;
+                    if (mTransformationInfo != null) {
+                        mTransformationInfo.mMatrixDirty = true;
+                    }
                 }
                 onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
             }
@@ -11747,14 +11827,22 @@
                     + "two integers");
         }
 
-        location[0] = mLeft + (int) (mTranslationX + 0.5f);
-        location[1] = mTop + (int) (mTranslationY + 0.5f);
+        location[0] = mLeft;
+        location[1] = mTop;
+        if (mTransformationInfo != null) {
+            location[0] += (int) (mTransformationInfo.mTranslationX + 0.5f);
+            location[1] += (int) (mTransformationInfo.mTranslationY + 0.5f);
+        }
 
         ViewParent viewParent = mParent;
         while (viewParent instanceof View) {
             final View view = (View)viewParent;
-            location[0] += view.mLeft + (int) (view.mTranslationX + 0.5f) - view.mScrollX;
-            location[1] += view.mTop + (int) (view.mTranslationY + 0.5f) - view.mScrollY;
+            location[0] += view.mLeft - view.mScrollX;
+            location[1] += view.mTop - view.mScrollY;
+            if (view.mTransformationInfo != null) {
+                location[0] += (int) (view.mTransformationInfo.mTranslationX + 0.5f);
+                location[1] += (int) (view.mTransformationInfo.mTranslationY + 0.5f);
+            }
             viewParent = view.mParent;
         }
 
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index 6ed49ee..84dc7d8 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -51,7 +51,7 @@
      * The View whose properties are being animated by this class. This is set at
      * construction time.
      */
-    private View mView;
+    private final View mView;
 
     /**
      * The duration of the underlying Animator object. By default, we don't set the duration
@@ -225,6 +225,7 @@
      */
     ViewPropertyAnimator(View view) {
         mView = view;
+        view.ensureTransformationInfo();
     }
 
     /**
@@ -721,36 +722,37 @@
      * @param value The value to set the property to
      */
     private void setValue(int propertyConstant, float value) {
+        final View.TransformationInfo info = mView.mTransformationInfo;
         switch (propertyConstant) {
             case TRANSLATION_X:
-                mView.mTranslationX = value;
+                info.mTranslationX = value;
                 break;
             case TRANSLATION_Y:
-                mView.mTranslationY = value;
+                info.mTranslationY = value;
                 break;
             case ROTATION:
-                mView.mRotation = value;
+                info.mRotation = value;
                 break;
             case ROTATION_X:
-                mView.mRotationX = value;
+                info.mRotationX = value;
                 break;
             case ROTATION_Y:
-                mView.mRotationY = value;
+                info.mRotationY = value;
                 break;
             case SCALE_X:
-                mView.mScaleX = value;
+                info.mScaleX = value;
                 break;
             case SCALE_Y:
-                mView.mScaleY = value;
+                info.mScaleY = value;
                 break;
             case X:
-                mView.mTranslationX = value - mView.mLeft;
+                info.mTranslationX = value - mView.mLeft;
                 break;
             case Y:
-                mView.mTranslationY = value - mView.mTop;
+                info.mTranslationY = value - mView.mTop;
                 break;
             case ALPHA:
-                mView.mAlpha = value;
+                info.mAlpha = value;
                 break;
         }
     }
@@ -762,27 +764,28 @@
      * @return float The value of the named property
      */
     private float getValue(int propertyConstant) {
+        final View.TransformationInfo info = mView.mTransformationInfo;
         switch (propertyConstant) {
             case TRANSLATION_X:
-                return mView.mTranslationX;
+                return info.mTranslationX;
             case TRANSLATION_Y:
-                return mView.mTranslationY;
+                return info.mTranslationY;
             case ROTATION:
-                return mView.mRotation;
+                return info.mRotation;
             case ROTATION_X:
-                return mView.mRotationX;
+                return info.mRotationX;
             case ROTATION_Y:
-                return mView.mRotationY;
+                return info.mRotationY;
             case SCALE_X:
-                return mView.mScaleX;
+                return info.mScaleX;
             case SCALE_Y:
-                return mView.mScaleY;
+                return info.mScaleY;
             case X:
-                return mView.mLeft + mView.mTranslationX;
+                return mView.mLeft + info.mTranslationX;
             case Y:
-                return mView.mTop + mView.mTranslationY;
+                return mView.mTop + info.mTranslationY;
             case ALPHA:
-                return mView.mAlpha;
+                return info.mAlpha;
         }
         return 0;
     }
@@ -861,7 +864,7 @@
                 }
             }
             if ((propertyMask & TRANSFORM_MASK) != 0) {
-                mView.mMatrixDirty = true;
+                mView.mTransformationInfo.mMatrixDirty = true;
                 mView.mPrivateFlags |= View.DRAWN; // force another invalidation
             }
             // invalidate(false) in all cases except if alphaHandled gets set to true
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index cf0971f..b948114 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9559,8 +9559,8 @@
 
                 TextView.this.getPositionListener().removeSubscriber(SuggestionsPopupWindow.this);
 
-                if ((mText instanceof Editable) && mSuggestionRangeSpan != null) {
-                    ((Editable) mText).removeSpan(mSuggestionRangeSpan);
+                if ((mText instanceof Spannable)) {
+                    ((Spannable) mText).removeSpan(mSuggestionRangeSpan);
                 }
 
                 setCursorVisible(mCursorWasVisibleBeforeSuggestions);
@@ -9744,7 +9744,7 @@
         }
 
         private boolean updateSuggestions() {
-            Spannable spannable = (Spannable)TextView.this.mText;
+            Spannable spannable = (Spannable) TextView.this.mText;
             SuggestionSpan[] suggestionSpans = getSuggestionSpans();
 
             final int nbSpans = suggestionSpans.length;
@@ -9754,6 +9754,7 @@
             int spanUnionEnd = 0;
 
             SuggestionSpan misspelledSpan = null;
+            int underlineColor = 0;
 
             for (int spanIndex = 0; spanIndex < nbSpans; spanIndex++) {
                 SuggestionSpan suggestionSpan = suggestionSpans[spanIndex];
@@ -9766,6 +9767,9 @@
                     misspelledSpan = suggestionSpan;
                 }
 
+                // The first span dictates the background color of the highlighted text
+                if (spanIndex == 0) underlineColor = suggestionSpan.getUnderlineColor();
+
                 String[] suggestions = suggestionSpan.getSuggestions();
                 int nbSuggestions = suggestions.length;
                 for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) {
@@ -9808,9 +9812,17 @@
 
             if (mNumberOfSuggestions == 0) return false;
 
-            if (mSuggestionRangeSpan == null) mSuggestionRangeSpan =
-                    new SuggestionRangeSpan(mHighlightColor);
-            ((Editable) mText).setSpan(mSuggestionRangeSpan, spanUnionStart, spanUnionEnd,
+            if (mSuggestionRangeSpan == null) mSuggestionRangeSpan = new SuggestionRangeSpan();
+            if (underlineColor == 0) {
+                // Fallback on the default highlight color when the first span does not provide one
+                mSuggestionRangeSpan.setBackgroundColor(mHighlightColor);
+            } else {
+                final float BACKGROUND_TRANSPARENCY = 0.3f;
+                final int newAlpha = (int) (Color.alpha(underlineColor) * BACKGROUND_TRANSPARENCY);
+                mSuggestionRangeSpan.setBackgroundColor(
+                        (underlineColor & 0x00FFFFFF) + (newAlpha << 24));
+            }
+            spannable.setSpan(mSuggestionRangeSpan, spanUnionStart, spanUnionEnd,
                     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
 
             mSuggestionsAdapter.notifyDataSetChanged();
@@ -9902,6 +9914,9 @@
                                     suggestionSpansFlags[i]);
                         }
                     }
+                    
+                    // Move cursor at the end of the replacement word
+                    Selection.setSelection(editable, spanEnd + lengthDifference);
                 }
             }
             hide();
diff --git a/core/jni/android_bluetooth_common.h b/core/jni/android_bluetooth_common.h
index 2f5fd5a..1f4da3a 100644
--- a/core/jni/android_bluetooth_common.h
+++ b/core/jni/android_bluetooth_common.h
@@ -202,6 +202,13 @@
 #define INPUT_OPERATION_GENERIC_FAILURE        5003
 #define INPUT_OPERATION_SUCCESS                5004
 
+#define HEALTH_OPERATION_SUCCESS               6000
+#define HEALTH_OPERATION_ERROR                 6001
+#define HEALTH_OPERATION_INVALID_ARGS          6002
+#define HEALTH_OPERATION_GENERIC_FAILURE       6003
+#define HEALTH_OPERATION_NOT_FOUND             6004
+#define HEALTH_OPERATION_NOT_ALLOWED           6005
+
 #endif
 } /* namespace android */
 
diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp
index eee256a..0335ce7 100644
--- a/core/jni/android_server_BluetoothEventLoop.cpp
+++ b/core/jni/android_server_BluetoothEventLoop.cpp
@@ -74,6 +74,7 @@
 static jmethodID method_onPanDeviceConnectionResult;
 static jmethodID method_onHealthDevicePropertyChanged;
 static jmethodID method_onHealthDeviceChannelChanged;
+static jmethodID method_onHealthDeviceConnectionResult;
 
 typedef event_loop_native_data_t native_data_t;
 
@@ -141,6 +142,9 @@
                                                "(Ljava/lang/String;[Ljava/lang/String;)V");
     method_onPanDeviceConnectionResult = env->GetMethodID(clazz, "onPanDeviceConnectionResult",
                                                "(Ljava/lang/String;I)V");
+    method_onHealthDeviceConnectionResult = env->GetMethodID(clazz,
+                                                             "onHealthDeviceConnectionResult",
+                                                             "(II)V");
     method_onHealthDevicePropertyChanged = env->GetMethodID(clazz, "onHealthDevicePropertyChanged",
                                                "(Ljava/lang/String;[Ljava/lang/String;)V");
     method_onHealthDeviceChannelChanged = env->GetMethodID(clazz, "onHealthDeviceChannelChanged",
@@ -1533,6 +1537,39 @@
     free(user);
 }
 
+void onHealthDeviceConnectionResult(DBusMessage *msg, void *user, void *n) {
+    LOGV("%s", __FUNCTION__);
+
+    native_data_t *nat = (native_data_t *)n;
+    DBusError err;
+    dbus_error_init(&err);
+    JNIEnv *env;
+    nat->vm->GetEnv((void**)&env, nat->envVer);
+
+    jint result = HEALTH_OPERATION_SUCCESS;
+    if (dbus_set_error_from_message(&err, msg)) {
+        if (!strcmp(err.name, BLUEZ_ERROR_IFC ".InvalidArgs")) {
+            result = HEALTH_OPERATION_INVALID_ARGS;
+        } else if (!strcmp(err.name, BLUEZ_ERROR_IFC ".HealthError")) {
+            result = HEALTH_OPERATION_ERROR;
+        } else if (!strcmp(err.name, BLUEZ_ERROR_IFC ".NotFound")) {
+            result = HEALTH_OPERATION_NOT_FOUND;
+        } else if (!strcmp(err.name, BLUEZ_ERROR_IFC ".NotAllowed")) {
+            result = HEALTH_OPERATION_NOT_ALLOWED;
+        } else {
+            result = HEALTH_OPERATION_GENERIC_FAILURE;
+        }
+        LOG_AND_FREE_DBUS_ERROR(&err);
+    }
+
+    LOGV("... Health Device Code = %d, result = %d", code, result);
+    jint code = *(int *) user;
+    env->CallVoidMethod(nat->me,
+                        method_onHealthDeviceConnectionResult,
+                        code,
+                        result);
+    free(user);
+}
 #endif
 
 static JNINativeMethod sMethods[] = {
diff --git a/core/jni/android_server_BluetoothService.cpp b/core/jni/android_server_BluetoothService.cpp
index 292047b..a49c918 100644
--- a/core/jni/android_server_BluetoothService.cpp
+++ b/core/jni/android_server_BluetoothService.cpp
@@ -78,8 +78,8 @@
 void onDiscoverServicesResult(DBusMessage *msg, void *user, void *nat);
 void onCreateDeviceResult(DBusMessage *msg, void *user, void *nat);
 void onInputDeviceConnectionResult(DBusMessage *msg, void *user, void *nat);
-void onConnectPanResult(DBusMessage *msg, void *user, void *n);
 void onPanDeviceConnectionResult(DBusMessage *msg, void *user, void *nat);
+void onHealthDeviceConnectionResult(DBusMessage *msg, void *user, void *nat);
 
 
 /** Get native data stored in the opaque (Java code maintained) pointer mNativeData
@@ -1450,79 +1450,70 @@
 }
 
 static jboolean createChannelNative(JNIEnv *env, jobject object,
-                                       jstring devicePath, jstring appPath, jstring config) {
+                                       jstring devicePath, jstring appPath, jstring config,
+                                       jint code) {
     LOGV("%s", __FUNCTION__);
-    jboolean result = JNI_FALSE;
 #ifdef HAVE_BLUETOOTH
     native_data_t *nat = get_native_data(env, object);
+    jobject eventLoop = env->GetObjectField(object, field_mEventLoop);
+    struct event_loop_native_data_t *eventLoopNat =
+            get_EventLoop_native_data(env, eventLoop);
 
-    if (nat) {
-        DBusError err;
-        dbus_error_init(&err);
-
+    if (nat && eventLoopNat) {
         const char *c_device_path = env->GetStringUTFChars(devicePath, NULL);
         const char *c_app_path = env->GetStringUTFChars(appPath, NULL);
         const char *c_config = env->GetStringUTFChars(config, NULL);
+        int *data = (int *) malloc(sizeof(int));
+        if (data == NULL) return JNI_FALSE;
 
-        DBusMessage *reply  = dbus_func_args(env, nat->conn,
-                                             c_device_path,
-                                             DBUS_HEALTH_DEVICE_IFACE,
-                                             "CreateChannel",
-                                             DBUS_TYPE_OBJECT_PATH, &c_app_path,
-                                             DBUS_TYPE_STRING, &c_config,
-                                             DBUS_TYPE_INVALID);
+        *data = code;
+        bool ret = dbus_func_args_async(env, nat->conn, -1, onHealthDeviceConnectionResult,
+                                        data, eventLoopNat, c_device_path,
+                                        DBUS_HEALTH_DEVICE_IFACE, "CreateChannel",
+                                        DBUS_TYPE_OBJECT_PATH, &c_app_path,
+                                        DBUS_TYPE_STRING, &c_config,
+                                        DBUS_TYPE_INVALID);
 
 
         env->ReleaseStringUTFChars(devicePath, c_device_path);
         env->ReleaseStringUTFChars(appPath, c_app_path);
         env->ReleaseStringUTFChars(config, c_config);
 
-        if (!reply) {
-            if (dbus_error_is_set(&err)) {
-                LOG_AND_FREE_DBUS_ERROR(&err);
-            }
-        } else {
-            result = JNI_TRUE;
-        }
+        return ret ? JNI_TRUE : JNI_FALSE;
     }
 #endif
-    return result;
+    return JNI_FALSE;
 }
 
 static jboolean destroyChannelNative(JNIEnv *env, jobject object, jstring devicePath,
-                                     jstring channelPath) {
+                                     jstring channelPath, jint code) {
     LOGE("%s", __FUNCTION__);
-    jboolean result = JNI_FALSE;
 #ifdef HAVE_BLUETOOTH
     native_data_t *nat = get_native_data(env, object);
+    jobject eventLoop = env->GetObjectField(object, field_mEventLoop);
+    struct event_loop_native_data_t *eventLoopNat =
+            get_EventLoop_native_data(env, eventLoop);
 
-    if (nat) {
-        DBusError err;
-        dbus_error_init(&err);
-
+    if (nat && eventLoopNat) {
         const char *c_device_path = env->GetStringUTFChars(devicePath, NULL);
         const char *c_channel_path = env->GetStringUTFChars(channelPath, NULL);
+        int *data = (int *) malloc(sizeof(int));
+        if (data == NULL) return JNI_FALSE;
 
-        DBusMessage *reply = dbus_func_args(env, nat->conn,
-                                            c_device_path,
-                                            DBUS_HEALTH_DEVICE_IFACE,
-                                            "DestroyChannel",
-                                            DBUS_TYPE_OBJECT_PATH, &c_channel_path,
-                                            DBUS_TYPE_INVALID);
+        *data = code;
+        bool ret = dbus_func_args_async(env, nat->conn, -1, onHealthDeviceConnectionResult,
+                                        data, eventLoopNat, c_device_path,
+                                        DBUS_HEALTH_DEVICE_IFACE, "DestroyChannel",
+                                        DBUS_TYPE_OBJECT_PATH, &c_channel_path,
+                                        DBUS_TYPE_INVALID);
 
         env->ReleaseStringUTFChars(devicePath, c_device_path);
         env->ReleaseStringUTFChars(channelPath, c_channel_path);
 
-        if (!reply) {
-            if (dbus_error_is_set(&err)) {
-                LOG_AND_FREE_DBUS_ERROR(&err);
-            }
-        } else {
-            result = JNI_TRUE;
-        }
+        return ret ? JNI_TRUE : JNI_FALSE;
     }
 #endif
-    return result;
+    return JNI_FALSE;
 }
 
 static jstring getMainChannelNative(JNIEnv *env, jobject object, jstring devicePath) {
@@ -1755,9 +1746,10 @@
 
     {"unregisterHealthApplicationNative", "(Ljava/lang/String;)Z",
               (void *)unregisterHealthApplicationNative},
-    {"createChannelNative", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
+    {"createChannelNative", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z",
               (void *)createChannelNative},
-    {"destroyChannelNative", "(Ljava/lang/String;Ljava/lang/String;)Z", (void *)destroyChannelNative},
+    {"destroyChannelNative", "(Ljava/lang/String;Ljava/lang/String;I)Z",
+              (void *)destroyChannelNative},
     {"getMainChannelNative", "(Ljava/lang/String;)Ljava/lang/String;", (void *)getMainChannelNative},
     {"getChannelApplicationNative", "(Ljava/lang/String;)Ljava/lang/String;",
               (void *)getChannelApplicationNative},
diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java
index 66ed104..a837294 100644
--- a/graphics/java/android/graphics/Matrix.java
+++ b/graphics/java/android/graphics/Matrix.java
@@ -37,6 +37,188 @@
     public static final int MPERSP_1 = 7;   //!< use with getValues/setValues
     public static final int MPERSP_2 = 8;   //!< use with getValues/setValues
 
+    /** @hide */
+    public static Matrix IDENTITY_MATRIX = new Matrix() {
+        void oops() {
+            throw new IllegalStateException("Matrix can not be modified");
+        }
+
+        @Override
+        public void set(Matrix src) {
+            oops();
+        }
+
+        @Override
+        public void reset() {
+            oops();
+        }
+
+        @Override
+        public void setTranslate(float dx, float dy) {
+            oops();
+        }
+
+        @Override
+        public void setScale(float sx, float sy, float px, float py) {
+            oops();
+        }
+
+        @Override
+        public void setScale(float sx, float sy) {
+            oops();
+        }
+
+        @Override
+        public void setRotate(float degrees, float px, float py) {
+            oops();
+        }
+
+        @Override
+        public void setRotate(float degrees) {
+            oops();
+        }
+
+        @Override
+        public void setSinCos(float sinValue, float cosValue, float px, float py) {
+            oops();
+        }
+
+        @Override
+        public void setSinCos(float sinValue, float cosValue) {
+            oops();
+        }
+
+        @Override
+        public void setSkew(float kx, float ky, float px, float py) {
+            oops();
+        }
+
+        @Override
+        public void setSkew(float kx, float ky) {
+            oops();
+        }
+
+        @Override
+        public boolean setConcat(Matrix a, Matrix b) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preTranslate(float dx, float dy) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preScale(float sx, float sy, float px, float py) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preScale(float sx, float sy) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preRotate(float degrees, float px, float py) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preRotate(float degrees) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preSkew(float kx, float ky, float px, float py) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preSkew(float kx, float ky) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preConcat(Matrix other) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postTranslate(float dx, float dy) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postScale(float sx, float sy, float px, float py) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postScale(float sx, float sy) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postRotate(float degrees, float px, float py) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postRotate(float degrees) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postSkew(float kx, float ky, float px, float py) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postSkew(float kx, float ky) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postConcat(Matrix other) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean setRectToRect(RectF src, RectF dst, ScaleToFit stf) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean setPolyToPoly(float[] src, int srcIndex, float[] dst, int dstIndex,
+                int pointCount) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public void setValues(float[] values) {
+            oops();
+        }
+    };
+
     /**
      * @hide
      */
diff --git a/include/media/mediascanner.h b/include/media/mediascanner.h
index 803bffb..a73403b 100644
--- a/include/media/mediascanner.h
+++ b/include/media/mediascanner.h
@@ -62,12 +62,17 @@
 private:
     // current locale (like "ja_JP"), created/destroyed with strdup()/free()
     char *mLocale;
+    char *mSkipList;
+    int *mSkipIndex;
 
     MediaScanResult doProcessDirectory(
             char *path, int pathRemaining, MediaScannerClient &client, bool noMedia);
     MediaScanResult doProcessDirectoryEntry(
             char *path, int pathRemaining, MediaScannerClient &client, bool noMedia,
             struct dirent* entry, char* fileSpot);
+    void loadSkipList();
+    bool shouldSkipDirectory(char *path);
+
 
     MediaScanner(const MediaScanner &);
     MediaScanner &operator=(const MediaScanner &);
@@ -103,4 +108,3 @@
 }; // namespace android
 
 #endif // MEDIASCANNER_H
-
diff --git a/media/libmedia/MediaScanner.cpp b/media/libmedia/MediaScanner.cpp
index 41f8593..19dedfc 100644
--- a/media/libmedia/MediaScanner.cpp
+++ b/media/libmedia/MediaScanner.cpp
@@ -16,6 +16,7 @@
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "MediaScanner"
+#include <cutils/properties.h>
 #include <utils/Log.h>
 
 #include <media/mediascanner.h>
@@ -26,11 +27,14 @@
 namespace android {
 
 MediaScanner::MediaScanner()
-    : mLocale(NULL) {
+    : mLocale(NULL), mSkipList(NULL), mSkipIndex(NULL) {
+    loadSkipList();
 }
 
 MediaScanner::~MediaScanner() {
     setLocale(NULL);
+    free(mSkipList);
+    free(mSkipIndex);
 }
 
 void MediaScanner::setLocale(const char *locale) {
@@ -47,6 +51,33 @@
     return mLocale;
 }
 
+void MediaScanner::loadSkipList() {
+    mSkipList = (char *)malloc(PROPERTY_VALUE_MAX * sizeof(char));
+    if (mSkipList) {
+      property_get("testing.mediascanner.skiplist", mSkipList, "");
+    }
+    if (!mSkipList || (strlen(mSkipList) == 0)) {
+        free(mSkipList);
+        mSkipList = NULL;
+        return;
+    }
+    mSkipIndex = (int *)malloc(PROPERTY_VALUE_MAX * sizeof(int));
+    if (mSkipIndex) {
+        // dup it because strtok will modify the string
+        char *skipList = strdup(mSkipList);
+        if (skipList) {
+            char * path = strtok(skipList, ",");
+            int i = 0;
+            while (path) {
+                mSkipIndex[i++] = strlen(path);
+                path = strtok(NULL, ",");
+            }
+            mSkipIndex[i] = -1;
+            free(skipList);
+        }
+    }
+}
+
 MediaScanResult MediaScanner::processDirectory(
         const char *path, MediaScannerClient &client) {
     int pathLength = strlen(path);
@@ -75,12 +106,39 @@
     return result;
 }
 
+bool MediaScanner::shouldSkipDirectory(char *path) {
+    if (path && mSkipList && mSkipIndex) {
+        int len = strlen(path);
+        int idx = 0;
+        // track the start position of next path in the comma
+        // separated list obtained from getprop
+        int startPos = 0;
+        while (mSkipIndex[idx] != -1) {
+            // no point to match path name if strlen mismatch
+            if ((len == mSkipIndex[idx])
+                // pick out the path segment from comma separated list
+                // to compare against current path parameter
+                && (strncmp(path, &mSkipList[startPos], len) == 0)) {
+                return true;
+            }
+            startPos += mSkipIndex[idx] + 1; // extra char for the delimiter
+            idx++;
+        }
+    }
+    return false;
+}
+
 MediaScanResult MediaScanner::doProcessDirectory(
         char *path, int pathRemaining, MediaScannerClient &client, bool noMedia) {
     // place to copy file or directory name
     char* fileSpot = path + strlen(path);
     struct dirent* entry;
 
+    if (shouldSkipDirectory(path)) {
+      LOGD("Skipping: %s", path);
+      return MEDIA_SCAN_RESULT_OK;
+    }
+
     // Treat all files as non-media in directories that contain a  ".nomedia" file
     if (pathRemaining >= 8 /* strlen(".nomedia") */ ) {
         strcpy(fileSpot, ".nomedia");
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaProfileReader.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaProfileReader.java
index b1ad315..b1d049e 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaProfileReader.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaProfileReader.java
@@ -95,6 +95,25 @@
         return audioEncoderMap.get(audioEncoder);
     }
 
+    public static int getMinFrameRateForCodec(int codec) {
+        return getMinOrMaxFrameRateForCodec(codec, false);
+    }
+
+    public static int getMaxFrameRateForCodec(int codec) {
+        return getMinOrMaxFrameRateForCodec(codec, true);
+    }
+
+    private static int getMinOrMaxFrameRateForCodec(int codec, boolean max) {
+        for (VideoEncoderCap cap: videoEncoders) {
+            if (cap.mCodec == codec) {
+                if (max) return cap.mMaxFrameRate;
+                else return cap.mMinFrameRate;
+            }
+        }
+        // Should never reach here
+        throw new IllegalArgumentException("Unsupported video codec " + codec);
+    }
+
     private MediaProfileReader() {} // Don't call me
 
     private static void initVideoEncoderMap() {
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaRecorderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaRecorderTest.java
index 82df6690..796b52c 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaRecorderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaRecorderTest.java
@@ -247,7 +247,9 @@
             mCamera.unlock();
             mRecorder.setCamera(mCamera);
             Thread.sleep(1000);
-            recordVideo(15, 352, 288, MediaRecorder.VideoEncoder.H263,
+            int codec = MediaRecorder.VideoEncoder.H263;
+            int frameRate = MediaProfileReader.getMaxFrameRateForCodec(codec);
+            recordVideo(frameRate, 352, 288, codec,
                     MediaRecorder.OutputFormat.THREE_GPP, 
                     MediaNames.RECORDED_PORTRAIT_H263, true);
             mCamera.lock();
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
index 0f79515..0b887b9 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
@@ -365,16 +365,6 @@
         assertTrue("H264 playback memory test", memoryResult);
     }
 
-    private int getMaxFrameRateForVideoEncoder(int codec) {
-        int frameRate = -1;
-        for (VideoEncoderCap cap: videoEncoders) {
-            if (cap.mCodec == MediaRecorder.VideoEncoder.H263) {
-                frameRate = cap.mMaxFrameRate;
-            }
-        }
-        return frameRate;
-    }
-
     // Test case 4: Capture the memory usage after every 20 video only recorded
     @LargeTest
     public void testH263RecordVideoOnlyMemoryUsage() throws Exception {
@@ -384,7 +374,7 @@
         File videoH263RecordOnlyMemoryOut = new File(MEDIA_MEMORY_OUTPUT);
         Writer output = new BufferedWriter(new FileWriter(videoH263RecordOnlyMemoryOut, true));
         output.write("H263 video record only\n");
-        int frameRate = getMaxFrameRateForVideoEncoder(MediaRecorder.VideoEncoder.H263);
+        int frameRate = MediaProfileReader.getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H263);
         assertTrue("H263 video recording frame rate", frameRate != -1);
         for (int i = 0; i < NUM_STRESS_LOOP; i++) {
             assertTrue(stressVideoRecord(frameRate, 352, 288, MediaRecorder.VideoEncoder.H263,
@@ -406,7 +396,7 @@
         File videoMp4RecordOnlyMemoryOut = new File(MEDIA_MEMORY_OUTPUT);
         Writer output = new BufferedWriter(new FileWriter(videoMp4RecordOnlyMemoryOut, true));
         output.write("MPEG4 video record only\n");
-        int frameRate = getMaxFrameRateForVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);
+        int frameRate = MediaProfileReader.getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.MPEG_4_SP);
         assertTrue("MPEG4 video recording frame rate", frameRate != -1);
         for (int i = 0; i < NUM_STRESS_LOOP; i++) {
             assertTrue(stressVideoRecord(frameRate, 352, 288, MediaRecorder.VideoEncoder.MPEG_4_SP,
@@ -428,7 +418,7 @@
 
         File videoRecordAudioMemoryOut = new File(MEDIA_MEMORY_OUTPUT);
         Writer output = new BufferedWriter(new FileWriter(videoRecordAudioMemoryOut, true));
-        int frameRate = getMaxFrameRateForVideoEncoder(MediaRecorder.VideoEncoder.H263);
+        int frameRate = MediaProfileReader.getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H263);
         assertTrue("H263 video recording frame rate", frameRate != -1);
         output.write("Audio and h263 video record\n");
         for (int i = 0; i < NUM_STRESS_LOOP; i++) {