Add elevation, Z properties to View

Change-Id: I3dd3b683a66e248a0fdf2ca69d1e962615b0daf9
diff --git a/api/current.txt b/api/current.txt
index 8466443..ff67634 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -491,6 +491,7 @@
     field public static final int editTextStyle = 16842862; // 0x101006e
     field public static final deprecated int editable = 16843115; // 0x101016b
     field public static final int editorExtras = 16843300; // 0x1010224
+    field public static final int elevation = 16843851; // 0x101044b
     field public static final int ellipsize = 16842923; // 0x10100ab
     field public static final int ems = 16843096; // 0x1010158
     field public static final int enabled = 16842766; // 0x101000e
@@ -30121,6 +30122,7 @@
     method public int getDrawingCacheQuality();
     method public void getDrawingRect(android.graphics.Rect);
     method public long getDrawingTime();
+    method public float getElevation();
     method public boolean getFilterTouchesWhenObscured();
     method public boolean getFitsSystemWindows();
     method public java.util.ArrayList<android.view.View> getFocusables(int);
@@ -30219,6 +30221,7 @@
     method public void getWindowVisibleDisplayFrame(android.graphics.Rect);
     method public float getX();
     method public float getY();
+    method public float getZ();
     method public boolean hasFocus();
     method public boolean hasFocusable();
     method public boolean hasNestedScrollingParent();
@@ -30385,6 +30388,7 @@
     method public void setDrawingCacheEnabled(boolean);
     method public void setDrawingCacheQuality(int);
     method public void setDuplicateParentStateEnabled(boolean);
+    method public void setElevation(float);
     method public void setEnabled(boolean);
     method public void setFadingEdgeLength(int);
     method public void setFilterTouchesWhenObscured(boolean);
@@ -30470,6 +30474,7 @@
     method public void setWillNotDraw(boolean);
     method public void setX(float);
     method public void setY(float);
+    method public void setZ(float);
     method public boolean showContextMenu();
     method public android.view.ActionMode startActionMode(android.view.ActionMode.Callback);
     method public void startAnimation(android.view.animation.Animation);
@@ -31049,6 +31054,8 @@
     method public android.view.ViewPropertyAnimator xBy(float);
     method public android.view.ViewPropertyAnimator y(float);
     method public android.view.ViewPropertyAnimator yBy(float);
+    method public android.view.ViewPropertyAnimator z(float);
+    method public android.view.ViewPropertyAnimator zBy(float);
   }
 
   public final class ViewStub extends android.view.View {
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index 30e4281..3dc09c4 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -443,6 +443,14 @@
         return nHasOverlappingRendering(mNativeRenderNode);
     }
 
+    public void setElevation(float lift) {
+        nSetElevation(mNativeRenderNode, lift);
+    }
+
+    public float getElevation() {
+        return nGetElevation(mNativeRenderNode);
+    }
+
     /**
      * Sets the translation value for the display list on the X axis.
      *
@@ -854,6 +862,7 @@
     private static native void nSetAlpha(long renderNode, float alpha);
     private static native void nSetHasOverlappingRendering(long renderNode,
             boolean hasOverlappingRendering);
+    private static native void nSetElevation(long renderNode, float lift);
     private static native void nSetTranslationX(long renderNode, float translationX);
     private static native void nSetTranslationY(long renderNode, float translationY);
     private static native void nSetTranslationZ(long renderNode, float translationZ);
@@ -874,6 +883,7 @@
     private static native float nGetCameraDistance(long renderNode);
     private static native float nGetScaleX(long renderNode);
     private static native float nGetScaleY(long renderNode);
+    private static native float nGetElevation(long renderNode);
     private static native float nGetTranslationX(long renderNode);
     private static native float nGetTranslationY(long renderNode);
     private static native float nGetTranslationZ(long renderNode);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index fdf31fa..463a2f7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3649,6 +3649,7 @@
         float tx = 0;
         float ty = 0;
         float tz = 0;
+        float elevation = 0;
         float rotation = 0;
         float rotationX = 0;
         float rotationY = 0;
@@ -3732,6 +3733,10 @@
                     tz = a.getDimensionPixelOffset(attr, 0);
                     transformSet = true;
                     break;
+                case com.android.internal.R.styleable.View_elevation:
+                    elevation = a.getDimensionPixelOffset(attr, 0);
+                    transformSet = true;
+                    break;
                 case com.android.internal.R.styleable.View_rotation:
                     rotation = a.getFloat(attr, 0);
                     transformSet = true;
@@ -4080,6 +4085,7 @@
             setTranslationX(tx);
             setTranslationY(ty);
             setTranslationZ(tz);
+            setElevation(elevation);
             setRotation(rotation);
             setRotationX(rotationX);
             setRotationY(rotationY);
@@ -10435,6 +10441,48 @@
         setTranslationY(y - mTop);
     }
 
+    /**
+     * The visual z position of this view, in pixels. This is equivalent to the
+     * {@link #setTranslationZ(float) translationZ} property plus the current
+     * {@link #getElevation() elevation} property.
+     *
+     * @return The visual z position of this view, in pixels.
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    public float getZ() {
+        return getElevation() + getTranslationZ();
+    }
+
+    /**
+     * Sets the visual z position of this view, in pixels. This is equivalent to setting the
+     * {@link #setTranslationZ(float) translationZ} property to be the difference between
+     * the x value passed in and the current {@link #getElevation() elevation} property.
+     *
+     * @param z The visual z position of this view, in pixels.
+     */
+    public void setZ(float z) {
+        setTranslationZ(z - getElevation());
+    }
+
+    @ViewDebug.ExportedProperty(category = "drawing")
+    public float getElevation() {
+        return mRenderNode.getElevation();
+    }
+
+    /**
+     * Sets the base depth location of this view.
+     *
+     * @attr ref android.R.styleable#View_elevation
+     */
+    public void setElevation(float elevation) {
+        if (elevation != getElevation()) {
+            invalidateViewProperty(true, false);
+            mRenderNode.setElevation(elevation);
+            invalidateViewProperty(false, true);
+
+            invalidateParentIfNeededAndWasQuickRejected();
+        }
+    }
 
     /**
      * The horizontal location of this view relative to its {@link #getLeft() left} position.
@@ -10502,9 +10550,9 @@
     }
 
     /**
-     * The depth location of this view relative to its parent.
+     * The depth location of this view relative to its {@link #getElevation() elevation}.
      *
-     * @return The depth of this view relative to its parent.
+     * @return The depth of this view relative to its elevation.
      */
     @ViewDebug.ExportedProperty(category = "drawing")
     public float getTranslationZ() {
@@ -10512,7 +10560,7 @@
     }
 
     /**
-     * Sets the depth location of this view relative to its parent.
+     * Sets the depth location of this view relative to its {@link #getElevation() elevation}.
      *
      * @attr ref android.R.styleable#View_translationZ
      */
@@ -11184,7 +11232,7 @@
             }
 
             // Damage the entire IsolatedZVolume recieving this view's shadow.
-            if (isHardwareAccelerated() && getTranslationZ() != 0) {
+            if (isHardwareAccelerated() && getZ() != 0) {
                 damageShadowReceiver();
             }
         }
@@ -11260,7 +11308,7 @@
         } else {
             damageInParent();
         }
-        if (isHardwareAccelerated() && invalidateParent && getTranslationZ() != 0) {
+        if (isHardwareAccelerated() && invalidateParent && getZ() != 0) {
             damageShadowReceiver();
         }
     }
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index 6b21451..b1aa7b2 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -144,10 +144,11 @@
     private static final int ROTATION_Y     = 0x0080;
     private static final int X              = 0x0100;
     private static final int Y              = 0x0200;
-    private static final int ALPHA          = 0x0400;
+    private static final int Z              = 0x0400;
+    private static final int ALPHA          = 0x0800;
 
     private static final int TRANSFORM_MASK = TRANSLATION_X | TRANSLATION_Y | TRANSLATION_Z |
-            SCALE_X | SCALE_Y | ROTATION | ROTATION_X | ROTATION_Y | X | Y;
+            SCALE_X | SCALE_Y | ROTATION | ROTATION_X | ROTATION_Y | X | Y | Z;
 
     /**
      * The mechanism by which the user can request several properties that are then animated
@@ -470,6 +471,32 @@
     }
 
     /**
+     * This method will cause the View's <code>z</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setZ(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator z(float value) {
+        animateProperty(Z, value);
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>z</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setZ(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator zBy(float value) {
+        animatePropertyBy(Z, value);
+        return this;
+    }
+
+    /**
      * This method will cause the View's <code>rotation</code> property to be animated to the
      * specified value. Animations already running on the property will be canceled.
      *
@@ -957,6 +984,9 @@
             case Y:
                 renderNode.setTranslationY(value - mView.mTop);
                 break;
+            case Z:
+                renderNode.setTranslationZ(value - renderNode.getElevation());
+                break;
             case ALPHA:
                 info.mAlpha = value;
                 renderNode.setAlpha(value);
@@ -993,6 +1023,8 @@
                 return mView.mLeft + node.getTranslationX();
             case Y:
                 return mView.mTop + node.getTranslationY();
+            case Z:
+                return node.getElevation() + node.getTranslationZ();
             case ALPHA:
                 return mView.mTransformationInfo.mAlpha;
         }
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 8dacfeb..3ad2ae5 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -164,6 +164,12 @@
     renderNode->mutateStagingProperties().setHasOverlappingRendering(hasOverlappingRendering);
 }
 
+static void android_view_RenderNode_setElevation(JNIEnv* env,
+        jobject clazz, jlong renderNodePtr, float elevation) {
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    renderNode->mutateStagingProperties().setElevation(elevation);
+}
+
 static void android_view_RenderNode_setTranslationX(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, float tx) {
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
@@ -331,6 +337,12 @@
     return renderNode->stagingProperties().getScaleY();
 }
 
+static jfloat android_view_RenderNode_getElevation(JNIEnv* env,
+        jobject clazz, jlong renderNodePtr) {
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    return renderNode->stagingProperties().getElevation();
+}
+
 static jfloat android_view_RenderNode_getTranslationX(JNIEnv* env,
         jobject clazz, jlong renderNodePtr) {
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
@@ -457,6 +469,7 @@
     { "nSetAlpha",             "(JF)V",  (void*) android_view_RenderNode_setAlpha },
     { "nSetHasOverlappingRendering", "(JZ)V",
             (void*) android_view_RenderNode_setHasOverlappingRendering },
+    { "nSetElevation",         "(JF)V",  (void*) android_view_RenderNode_setElevation },
     { "nSetTranslationX",      "(JF)V",  (void*) android_view_RenderNode_setTranslationX },
     { "nSetTranslationY",      "(JF)V",  (void*) android_view_RenderNode_setTranslationY },
     { "nSetTranslationZ",      "(JF)V",  (void*) android_view_RenderNode_setTranslationZ },
@@ -485,6 +498,7 @@
     { "nGetCameraDistance",       "(J)F",  (void*) android_view_RenderNode_getCameraDistance },
     { "nGetScaleX",               "(J)F",  (void*) android_view_RenderNode_getScaleX },
     { "nGetScaleY",               "(J)F",  (void*) android_view_RenderNode_getScaleY },
+    { "nGetElevation",            "(J)F",  (void*) android_view_RenderNode_getElevation },
     { "nGetTranslationX",         "(J)F",  (void*) android_view_RenderNode_getTranslationX },
     { "nGetTranslationY",         "(J)F",  (void*) android_view_RenderNode_getTranslationY },
     { "nGetTranslationZ",         "(J)F",  (void*) android_view_RenderNode_getTranslationZ },
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 69440be..abac60e 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2191,6 +2191,9 @@
              (completely opaque). -->
         <attr name="alpha" format="float" />
 
+        <!-- base z depth of the view -->
+        <attr name="elevation" format="dimension" />
+
         <!-- translation in x of the view. This value is added post-layout to the left
              property of the view, which is set by its layout. -->
         <attr name="translationX" format="dimension" />
@@ -2199,7 +2202,7 @@
              property of the view, which is set by its layout. -->
         <attr name="translationY" format="dimension" />
 
-        <!-- translation in z of the view. This value is added post-layout to its position. -->
+        <!-- translation in z of the view. This value is added to its elevation. -->
         <attr name="translationZ" format="dimension" />
 
         <!-- x location of the pivot point around which the view will rotate and scale.
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index f68f759..85ef004 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2163,6 +2163,7 @@
   <public type="attr" name="windowAllowEnterTransitionOverlap" />
   <public type="attr" name="sessionService" />
   <public type="attr" name="switchStyle" />
+  <public type="attr" name="elevation" />
 
   <public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
 
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 2008f02..838e5ac 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -219,11 +219,11 @@
         matrix.multiply(anim);
     }
 
-    bool applyTranslationZ = true3dTransform && !MathUtils::isZero(properties().getTranslationZ());
+    bool applyTranslationZ = true3dTransform && !MathUtils::isZero(properties().getZ());
     if (properties().hasTransformMatrix() || applyTranslationZ) {
         if (properties().isTransformTranslateOnly()) {
             matrix.translate(properties().getTranslationX(), properties().getTranslationY(),
-                    true3dTransform ? properties().getTranslationZ() : 0.0f);
+                    true3dTransform ? properties().getZ() : 0.0f);
         } else {
             if (!true3dTransform) {
                 matrix.multiply(*properties().getTransformMatrix());
@@ -232,7 +232,7 @@
                 true3dMat.loadTranslate(
                         properties().getPivotX() + properties().getTranslationX(),
                         properties().getPivotY() + properties().getTranslationY(),
-                        properties().getTranslationZ());
+                        properties().getZ());
                 true3dMat.rotate(properties().getRotationX(), 1, 0, 0);
                 true3dMat.rotate(properties().getRotationY(), 0, 1, 0);
                 true3dMat.rotate(properties().getRotation(), 0, 0, 1);
@@ -344,7 +344,9 @@
 
 void RenderNode::deferNodeTree(DeferStateStruct& deferStruct) {
     DeferOperationHandler handler(deferStruct, 0);
-    if (properties().getTranslationZ() > 0.0f) issueDrawShadowOperation(Matrix4::identity(), handler);
+    if (MathUtils::isPositive(properties().getZ())) {
+        issueDrawShadowOperation(Matrix4::identity(), handler);
+    }
     issueOperations<DeferOperationHandler>(deferStruct.mRenderer, handler);
 }
 
@@ -380,7 +382,9 @@
 
 void RenderNode::replayNodeTree(ReplayStateStruct& replayStruct) {
     ReplayOperationHandler handler(replayStruct, 0);
-    if (properties().getTranslationZ() > 0.0f) issueDrawShadowOperation(Matrix4::identity(), handler);
+    if (MathUtils::isPositive(properties().getZ())) {
+        issueDrawShadowOperation(Matrix4::identity(), handler);
+    }
     issueOperations<ReplayOperationHandler>(replayStruct.mRenderer, handler);
 }
 
@@ -395,7 +399,7 @@
     for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) {
         DrawDisplayListOp* childOp = mDisplayListData->children()[i];
         RenderNode* child = childOp->mDisplayList;
-        float childZ = child->properties().getTranslationZ();
+        float childZ = child->properties().getZ();
 
         if (!MathUtils::isZero(childZ)) {
             zTranslatedNodes.add(ZDrawDisplayListOpPair(childZ, childOp));
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
index a922db8..9ec7297 100644
--- a/libs/hwui/RenderProperties.cpp
+++ b/libs/hwui/RenderProperties.cpp
@@ -37,6 +37,7 @@
         , mProjectionReceiver(false)
         , mAlpha(1)
         , mHasOverlappingRendering(true)
+        , mElevation(0)
         , mTranslationX(0), mTranslationY(0), mTranslationZ(0)
         , mRotation(0), mRotationX(0), mRotationY(0)
         , mScaleX(1), mScaleY(1)
@@ -100,7 +101,7 @@
     if (hasTransformMatrix()) {
         if (isTransformTranslateOnly()) {
             ALOGD("%*sTranslate %.2f, %.2f, %.2f",
-                    level * 2, "", mPrimitiveFields.mTranslationX, mPrimitiveFields.mTranslationY, mPrimitiveFields.mTranslationZ);
+                    level * 2, "", getTranslationX(), getTranslationY(), getZ());
         } else {
             ALOGD("%*sConcatMatrix %p: " SK_MATRIX_STRING,
                     level * 2, "", mComputedFields.mTransformMatrix, SK_MATRIX_ARGS(mComputedFields.mTransformMatrix));
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index 4270da2..8fc2dd0 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -105,6 +105,17 @@
         return mPrimitiveFields.mHasOverlappingRendering;
     }
 
+    void setElevation(float elevation) {
+        if (elevation != mPrimitiveFields.mElevation) {
+            mPrimitiveFields.mElevation = elevation;
+            // mMatrixOrPivotDirty not set, since matrix doesn't respect Z
+        }
+    }
+
+    float getElevation() const {
+        return mPrimitiveFields.mElevation;
+    }
+
     void setTranslationX(float translationX) {
         if (translationX != mPrimitiveFields.mTranslationX) {
             mPrimitiveFields.mTranslationX = translationX;
@@ -130,7 +141,7 @@
     void setTranslationZ(float translationZ) {
         if (translationZ != mPrimitiveFields.mTranslationZ) {
             mPrimitiveFields.mTranslationZ = translationZ;
-            mPrimitiveFields.mMatrixOrPivotDirty = true;
+            // mMatrixOrPivotDirty not set, since matrix doesn't respect Z
         }
     }
 
@@ -138,6 +149,10 @@
         return mPrimitiveFields.mTranslationZ;
     }
 
+    float getZ() const {
+        return getElevation() + getTranslationZ();
+    }
+
     void setRotation(float rotation) {
         if (rotation != mPrimitiveFields.mRotation) {
             mPrimitiveFields.mRotation = rotation;
@@ -302,7 +317,8 @@
     }
 
     void setLeftTopRightBottom(int left, int top, int right, int bottom) {
-        if (left != mPrimitiveFields.mLeft || top != mPrimitiveFields.mTop || right != mPrimitiveFields.mRight || bottom != mPrimitiveFields.mBottom) {
+        if (left != mPrimitiveFields.mLeft || top != mPrimitiveFields.mTop
+                || right != mPrimitiveFields.mRight || bottom != mPrimitiveFields.mBottom) {
             mPrimitiveFields.mLeft = left;
             mPrimitiveFields.mTop = top;
             mPrimitiveFields.mRight = right;
@@ -429,6 +445,7 @@
         bool mProjectionReceiver;
         float mAlpha;
         bool mHasOverlappingRendering;
+        float mElevation;
         float mTranslationX, mTranslationY, mTranslationZ;
         float mRotation, mRotationX, mRotationY;
         float mScaleX, mScaleY;
diff --git a/libs/hwui/utils/MathUtils.h b/libs/hwui/utils/MathUtils.h
index 57ba8fa..7deabe9 100644
--- a/libs/hwui/utils/MathUtils.h
+++ b/libs/hwui/utils/MathUtils.h
@@ -29,6 +29,10 @@
     inline static bool isZero(float value) {
         return (value >= -gNonZeroEpsilon) && (value <= gNonZeroEpsilon);
     }
+
+    inline static bool isPositive(float value) {
+        return value >= gNonZeroEpsilon;
+    }
 }; // class MathUtils
 
 } /* namespace uirenderer */
diff --git a/tests/HwAccelerationTest/res/layout/projection_clipping.xml b/tests/HwAccelerationTest/res/layout/projection_clipping.xml
index 7caf90a..7177fc8f 100644
--- a/tests/HwAccelerationTest/res/layout/projection_clipping.xml
+++ b/tests/HwAccelerationTest/res/layout/projection_clipping.xml
@@ -6,7 +6,7 @@
     <FrameLayout
         android:translationX="50dp"
         android:translationY="50dp"
-        android:translationZ="30dp"
+        android:elevation="30dp"
         android:layout_width="200dp"
         android:layout_height="200dp"
         android:background="@drawable/round_rect_background">