Optimization of alpha with DisplayList properties

Some views (such as ImageView and TextView) handle non-opaque alpha
values directly. This was originally an optimization, but we can handle it faster
in many cases without this optimization when DisplayList properties are enabled.
Basically, if a view has non-overlapping rendering, we set the alpha value directly
on the renderer (the equivalent of setting it on the Paint object) and draw each
primitive with that alpha value. Doing it this way avoids re-creating DisplayLists
while getting the same speedup that onSetAlpha() used to get pre-DisplayList properties.

Change-Id: I0f7827f075d3b35093a882d4adbb300a1063c288
diff --git a/api/current.txt b/api/current.txt
index eba8a2c..ea93c62 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -23371,6 +23371,7 @@
     method public boolean hasFocus();
     method public boolean hasFocusable();
     method public boolean hasOnClickListeners();
+    method public boolean hasOverlappingRendering();
     method public boolean hasTransientState();
     method public boolean hasWindowFocus();
     method public static android.view.View inflate(android.content.Context, int, android.view.ViewGroup);
diff --git a/core/java/android/view/DisplayList.java b/core/java/android/view/DisplayList.java
index 33631b7..fba73fbd 100644
--- a/core/java/android/view/DisplayList.java
+++ b/core/java/android/view/DisplayList.java
@@ -149,6 +149,15 @@
     public abstract void setAlpha(float alpha);
 
     /**
+     * Sets whether the DisplayList renders content which overlaps. Non-overlapping rendering
+     * can use a fast path for alpha that avoids rendering to an offscreen buffer.
+     *
+     * @param hasOverlappingRendering
+     * @see android.view.View#hasOverlappingRendering()
+     */
+    public abstract void setHasOverlappingRendering(boolean hasOverlappingRendering);
+
+    /**
      * Sets the translationX value for the DisplayList
      *
      * @param translationX The translationX value of the DisplayList
diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java
index bc3bce0..f3618eb 100644
--- a/core/java/android/view/GLES20DisplayList.java
+++ b/core/java/android/view/GLES20DisplayList.java
@@ -147,6 +147,15 @@
     }
 
     @Override
+    public void setHasOverlappingRendering(boolean hasOverlappingRendering) {
+        try {
+            nSetHasOverlappingRendering(getNativeDisplayList(), hasOverlappingRendering);
+        } catch (IllegalStateException e) {
+            // invalid DisplayList okay: we'll set current values the next time we render to it
+        }
+    }
+
+    @Override
     public void setTranslationX(float translationX) {
         try {
             nSetTranslationX(getNativeDisplayList(), translationX);
@@ -335,6 +344,8 @@
     private static native void nSetClipChildren(int displayList, boolean clipChildren);
     private static native void nSetApplicationScale(int displayList, float scale);
     private static native void nSetAlpha(int displayList, float alpha);
+    private static native void nSetHasOverlappingRendering(int displayList,
+            boolean hasOverlappingRendering);
     private static native void nSetTranslationX(int displayList, float translationX);
     private static native void nSetTranslationY(int displayList, float translationY);
     private static native void nSetRotation(int displayList, float rotation);
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index b100a0c..9ef2621 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -134,7 +134,7 @@
     /**
      * Number of frames to profile.
      */
-    private static final int PROFILE_MAX_FRAMES = 64;
+    private static final int PROFILE_MAX_FRAMES = 120;
 
     /**
      * Number of floats per profiled frame.
@@ -1046,10 +1046,6 @@
                                 Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- getDisplayList() took " +
                                         total + "ms");
                             }
-                            if (View.USE_DISPLAY_LIST_PROPERTIES) {
-                                Log.d("DLProperties", "getDisplayList():\t" +
-                                        mProfileData[mProfileCurrentFrame]);
-                            }
                         }
 
                         if (displayList != null) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 69d7655..cecb445 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2830,19 +2830,6 @@
     private boolean mSendingHoverAccessibilityEvents;
 
     /**
-     * Delegate for injecting accessiblity functionality.
-     */
-    AccessibilityDelegate mAccessibilityDelegate;
-
-    /**
-     * Consistency verifier for debugging purposes.
-     * @hide
-     */
-    protected final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
-            InputEventConsistencyVerifier.isInstrumentationEnabled() ?
-                    new InputEventConsistencyVerifier(this, 0) : null;
-
-    /**
      * Simple constructor to use when creating a view from code.
      *
      * @param context The Context the view is running in, through which it can
@@ -2863,6 +2850,19 @@
     }
 
     /**
+     * Delegate for injecting accessiblity functionality.
+     */
+    AccessibilityDelegate mAccessibilityDelegate;
+
+    /**
+     * Consistency verifier for debugging purposes.
+     * @hide
+     */
+    protected final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
+            InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+                    new InputEventConsistencyVerifier(this, 0) : null;
+
+    /**
      * Constructor that is called when inflating a view from XML. This is called
      * when a view is being constructed from an XML file, supplying attributes
      * that were specified in the XML file. This version uses a default style of
@@ -7855,6 +7855,23 @@
     }
 
     /**
+     * Returns whether this View has content which overlaps. This function, intended to be
+     * overridden by specific View types, is an optimization when alpha is set on a view. If
+     * rendering overlaps in a view with alpha < 1, that view is drawn to an offscreen buffer
+     * and then composited it into place, which can be expensive. If the view has no overlapping
+     * rendering, the view can draw each primitive with the appropriate alpha value directly.
+     * An example of overlapping rendering is a TextView with a background image, such as a
+     * Button. An example of non-overlapping rendering is a TextView with no background, or
+     * an ImageView with only the foreground image. The default implementation returns true;
+     * subclasses should override if they have cases which can be optimized.
+     *
+     * @return true if the content in this view might overlap, false otherwise.
+     */
+    public boolean hasOverlappingRendering() {
+        return true;
+    }
+
+    /**
      * <p>Sets the opacity of the view. This is a value from 0 to 1, where 0 means the view is
      * completely transparent and 1 means the view is completely opaque.</p>
      *
@@ -11525,6 +11542,7 @@
     void setDisplayListProperties(DisplayList displayList) {
         if (USE_DISPLAY_LIST_PROPERTIES && displayList != null) {
             displayList.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
+            displayList.setHasOverlappingRendering(hasOverlappingRendering());
             if (mParent instanceof ViewGroup) {
                 displayList.setClipChildren(
                         (((ViewGroup)mParent).mGroupFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0);
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index 623b567..3626aba 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -834,40 +834,49 @@
      */
     private void setValue(int propertyConstant, float value) {
         final View.TransformationInfo info = mView.mTransformationInfo;
+        DisplayList displayList = View.USE_DISPLAY_LIST_PROPERTIES ? mView.mDisplayList : null;
         switch (propertyConstant) {
             case TRANSLATION_X:
                 info.mTranslationX = value;
+                if (displayList != null) displayList.setTranslationX(value);
                 break;
             case TRANSLATION_Y:
                 info.mTranslationY = value;
+                if (displayList != null) displayList.setTranslationY(value);
                 break;
             case ROTATION:
                 info.mRotation = value;
+                if (displayList != null) displayList.setRotation(value);
                 break;
             case ROTATION_X:
                 info.mRotationX = value;
+                if (displayList != null) displayList.setRotationX(value);
                 break;
             case ROTATION_Y:
                 info.mRotationY = value;
+                if (displayList != null) displayList.setRotationY(value);
                 break;
             case SCALE_X:
                 info.mScaleX = value;
+                if (displayList != null) displayList.setScaleX(value);
                 break;
             case SCALE_Y:
                 info.mScaleY = value;
+                if (displayList != null) displayList.setScaleY(value);
                 break;
             case X:
                 info.mTranslationX = value - mView.mLeft;
+                if (displayList != null) displayList.setTranslationX(value - mView.mLeft);
                 break;
             case Y:
                 info.mTranslationY = value - mView.mTop;
+                if (displayList != null) displayList.setTranslationY(value - mView.mTop);
                 break;
             case ALPHA:
                 info.mAlpha = value;
+                if (displayList != null) displayList.setAlpha(value);
                 break;
         }
-        // TODO: optimize to set only the properties that have changed
-        mView.setDisplayListProperties();
     }
 
     /**
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index b1a75e1..91e2e497 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -201,7 +201,7 @@
 
     @Override
     protected boolean onSetAlpha(int alpha) {
-        if (getBackground() == null) {
+        if (!USE_DISPLAY_LIST_PROPERTIES && getBackground() == null) {
             int scale = alpha + (alpha >> 7);
             if (mViewAlphaScale != scale) {
                 mViewAlphaScale = scale;
@@ -214,6 +214,15 @@
     }
 
     @Override
+    public boolean hasOverlappingRendering() {
+        if (!USE_DISPLAY_LIST_PROPERTIES) {
+            return super.hasOverlappingRendering();
+        } else {
+            return (getBackground() != null);
+        }
+    }
+
+    @Override
     public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
         super.onPopulateAccessibilityEvent(event);
         CharSequence contentDescription = getContentDescription();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 2a81f08..d2a1755 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4268,7 +4268,8 @@
     protected boolean onSetAlpha(int alpha) {
         // Alpha is supported if and only if the drawing can be done in one pass.
         // TODO text with spans with a background color currently do not respect this alpha.
-        if (getBackground() == null) {
+        if (!USE_DISPLAY_LIST_PROPERTIES &&
+                (getBackground() != null || mText instanceof Spannable || hasSelection())) {
             if (mCurrentAlpha != alpha) {
                 mCurrentAlpha = alpha;
                 final Drawables dr = mDrawables;
@@ -4292,6 +4293,15 @@
         return false;
     }
 
+    @Override
+    public boolean hasOverlappingRendering() {
+        if (!USE_DISPLAY_LIST_PROPERTIES) {
+            return super.hasOverlappingRendering();
+        } else {
+            return (getBackground() != null || mText instanceof Spannable || hasSelection());
+        }
+    }
+
     /**
      * When a TextView is used to display a useful piece of information to the user (such as a
      * contact's address), it should be made selectable, so that the user can select and copy this
diff --git a/core/jni/android_view_GLES20DisplayList.cpp b/core/jni/android_view_GLES20DisplayList.cpp
index 60fb6d4..b307a2f 100644
--- a/core/jni/android_view_GLES20DisplayList.cpp
+++ b/core/jni/android_view_GLES20DisplayList.cpp
@@ -65,6 +65,11 @@
     displayList->setAlpha(alpha);
 }
 
+static void android_view_GLES20DisplayList_setHasOverlappingRendering(JNIEnv* env,
+        jobject clazz, DisplayList* displayList, bool hasOverlappingRendering) {
+    displayList->setHasOverlappingRendering(hasOverlappingRendering);
+}
+
 static void android_view_GLES20DisplayList_setTranslationX(JNIEnv* env,
         jobject clazz, DisplayList* displayList, float tx) {
     displayList->setTranslationX(tx);
@@ -185,6 +190,8 @@
     { "nSetAnimationMatrix",   "(II)V",  (void*) android_view_GLES20DisplayList_setAnimationMatrix },
     { "nSetClipChildren",      "(IZ)V",  (void*) android_view_GLES20DisplayList_setClipChildren },
     { "nSetAlpha",             "(IF)V",  (void*) android_view_GLES20DisplayList_setAlpha },
+    { "nSetHasOverlappingRendering", "(IZ)V",
+            (void*) android_view_GLES20DisplayList_setHasOverlappingRendering },
     { "nSetTranslationX",      "(IF)V",  (void*) android_view_GLES20DisplayList_setTranslationX },
     { "nSetTranslationY",      "(IF)V",  (void*) android_view_GLES20DisplayList_setTranslationY },
     { "nSetRotation",          "(IF)V",  (void*) android_view_GLES20DisplayList_setRotation },
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index f37bfd2..9f2bacd 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -112,6 +112,7 @@
     mClipChildren = true;
     mAlpha = 1;
     mMultipliedAlpha = 255;
+    mHasOverlappingRendering = true;
     mTranslationX = 0;
     mTranslationY = 0;
     mRotation = 0;
@@ -772,18 +773,23 @@
             }
         }
         if (mAlpha < 1 && !mCaching) {
-            // TODO: should be able to store the size of a DL at record time and not
-            // have to pass it into this call. In fact, this information might be in the
-            // location/size info that we store with the new native transform data.
-            int flags = SkCanvas::kHasAlphaLayer_SaveFlag;
-            if (mClipChildren) {
-                flags |= SkCanvas::kClipToLayer_SaveFlag;
+            if (!mHasOverlappingRendering) {
+                DISPLAY_LIST_LOGD("%s%s %.2f", indent, "SetAlpha", mAlpha);
+                renderer.setAlpha(mAlpha);
+            } else {
+                // TODO: should be able to store the size of a DL at record time and not
+                // have to pass it into this call. In fact, this information might be in the
+                // location/size info that we store with the new native transform data.
+                int flags = SkCanvas::kHasAlphaLayer_SaveFlag;
+                if (mClipChildren) {
+                    flags |= SkCanvas::kClipToLayer_SaveFlag;
+                }
+                DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %.2f, %d, 0x%x", indent, "SaveLayerAlpha",
+                        (float) 0, (float) 0, (float) mRight - mLeft, (float) mBottom - mTop,
+                        mMultipliedAlpha, flags);
+                renderer.saveLayerAlpha(0, 0, mRight - mLeft, mBottom - mTop,
+                        mMultipliedAlpha, flags);
             }
-            DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %.2f, %d, 0x%x", indent, "SaveLayerAlpha",
-                    (float) 0, (float) 0, (float) mRight - mLeft, (float) mBottom - mTop,
-                    mMultipliedAlpha, flags);
-            renderer.saveLayerAlpha(0, 0, mRight - mLeft, mBottom - mTop,
-                    mMultipliedAlpha, flags);
         }
         if (mClipChildren) {
             DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %.2f", indent, "ClipRect", 0.0f, 0.0f,
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index 38b0a6d..fe0c94d 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -181,6 +181,10 @@
         }
     }
 
+    void setHasOverlappingRendering(bool hasOverlappingRendering) {
+        mHasOverlappingRendering = hasOverlappingRendering;
+    }
+
     void setTranslationX(float translationX) {
         if (translationX != mTranslationX) {
             mTranslationX = translationX;
@@ -496,6 +500,7 @@
     bool mClipChildren;
     float mAlpha;
     int mMultipliedAlpha;
+    bool mHasOverlappingRendering;
     float mTranslationX, mTranslationY;
     float mRotation, mRotationX, mRotationY;
     float mScaleX, mScaleY;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 115787c..ec9b56b 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1101,6 +1101,7 @@
 
 void OpenGLRenderer::setupDrawColor(int color, int alpha) {
     mColorA = alpha / 255.0f;
+    mColorA *= mSnapshot->alpha;
     // Second divide of a by 255 is an optimization, allowing us to simply multiply
     // the rgb values by a instead of also dividing by 255
     const float a = mColorA / 255.0f;
@@ -2800,6 +2801,7 @@
         *mode = SkXfermode::kSrcOver_Mode;
         *alpha = 255;
     }
+    *alpha *= mSnapshot->alpha;
 }
 
 SkXfermode::Mode OpenGLRenderer::getXfermode(SkXfermode* mode) {
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index b651904..ab137cc 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -85,6 +85,10 @@
     virtual int saveLayerAlpha(float left, float top, float right, float bottom,
             int alpha, int flags);
 
+    virtual void setAlpha(float alpha) {
+        mSnapshot->alpha = alpha;
+    }
+
     virtual void translate(float dx, float dy);
     virtual void rotate(float degrees);
     virtual void scale(float sx, float sy);
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index de2c674..5d5961a 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -26,7 +26,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 Snapshot::Snapshot(): flags(0), previous(NULL), layer(NULL), fbo(0),
-        invisible(false), empty(false) {
+        invisible(false), empty(false), alpha(1.0f) {
 
     transform = &mTransformRoot;
     clipRect = &mClipRectRoot;
@@ -41,7 +41,7 @@
 Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags):
         flags(0), previous(s), layer(NULL), fbo(s->fbo),
         invisible(s->invisible), empty(false),
-        viewport(s->viewport), height(s->height) {
+        viewport(s->viewport), height(s->height), alpha(s->alpha) {
 
     clipRegion = NULL;
 
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index b2bc879..30b03fc 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -208,6 +208,17 @@
      */
     Region* region;
 
+    /**
+     * Current alpha value. This value is 1 by default, but may be set by a DisplayList which
+     * has translucent rendering in a non-overlapping View. This value will be used by
+     * the renderer to set the alpha in the current color being used for ensuing drawing
+     * operations. The value is inherited by child snapshots because the same value should
+     * be applied to descendents of the current DisplayList (for example, a TextView contains
+     * the base alpha value which should be applied to the child DisplayLists used for drawing
+     * the actual text).
+     */
+    float alpha;
+
 private:
     void ensureClipRegion();
     void copyClipRectFromRegion();
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index f4c0841..ceda610 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -657,5 +657,14 @@
             </intent-filter>
         </activity>
 
+        <activity
+                android:name="ViewPropertyAlphaActivity"
+                android:label="_ViewPropAlpha">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
     </application>
 </manifest>
diff --git a/tests/HwAccelerationTest/res/layout/view_properties.xml b/tests/HwAccelerationTest/res/layout/view_properties.xml
new file mode 100644
index 0000000..d7ed819
--- /dev/null
+++ b/tests/HwAccelerationTest/res/layout/view_properties.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="fill_parent"
+              android:layout_height="fill_parent"
+              android:id="@+id/container">
+    <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Invalidate"
+            android:id="@+id/invalidateButton"/>
+    <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Button"
+            android:id="@+id/button"/>
+    <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Some text"
+            android:id="@+id/textview"/>
+    <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/spantext"/>
+    <EditText
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Edit text"
+            android:id="@+id/edittext"/>
+    <EditText
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Selected text"
+            android:id="@+id/selectedtext"/>
+    <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Some text"
+            android:background="#00ff00"
+            android:id="@+id/textviewbackground"/>
+    <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/icon"
+            android:id="@+id/imageview"/>
+    <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:id="@+id/layout">
+        <Button
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Button"/>
+        <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Some text"/>
+        <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Some text"
+                android:background="#00ff00"/>
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ViewPropertyAlphaActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewPropertyAlphaActivity.java
new file mode 100644
index 0000000..738801d
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewPropertyAlphaActivity.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.style.BackgroundColorSpan;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.ImageSpan;
+import android.text.style.SuggestionSpan;
+import android.text.style.UnderlineSpan;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class ViewPropertyAlphaActivity extends Activity {
+    
+    MyView myViewAlphaDefault, myViewAlphaHandled;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.view_properties);
+
+        getWindow().getDecorView().postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                startAnim(R.id.button);
+                startAnim(R.id.textview);
+                startAnim(R.id.spantext);
+                startAnim(R.id.edittext);
+                startAnim(R.id.selectedtext);
+                startAnim(R.id.textviewbackground);
+                startAnim(R.id.layout);
+                startAnim(R.id.imageview);
+                startAnim(myViewAlphaDefault);
+                startAnim(myViewAlphaHandled);
+                EditText selectedText = (EditText) findViewById(R.id.selectedtext);
+                selectedText.setSelection(3, 8);
+            }
+        }, 2000);
+        
+        Button invalidator = (Button) findViewById(R.id.invalidateButton);
+        invalidator.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                findViewById(R.id.textview).invalidate();
+                findViewById(R.id.spantext).invalidate();
+            }
+        });
+
+        TextView textView = (TextView) findViewById(R.id.spantext);
+        if (textView != null) {
+            SpannableStringBuilder text =
+                    new SpannableStringBuilder("Now this is a short text message with spans");
+
+            text.setSpan(new BackgroundColorSpan(Color.RED), 0, 3,
+                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            text.setSpan(new ForegroundColorSpan(Color.BLUE), 4, 9,
+                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            text.setSpan(new SuggestionSpan(this, new String[]{"longer"}, 3), 11, 16,
+                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            text.setSpan(new UnderlineSpan(), 17, 20,
+                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            text.setSpan(new ImageSpan(this, R.drawable.icon), 21, 22,
+                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+            textView.setText(text);
+        }
+        
+        LinearLayout container = (LinearLayout) findViewById(R.id.container);
+        myViewAlphaDefault = new MyView(this, false);
+        myViewAlphaDefault.setLayoutParams(new LinearLayout.LayoutParams(75, 75));
+        container.addView(myViewAlphaDefault);
+        myViewAlphaHandled = new MyView(this, true);
+        myViewAlphaHandled.setLayoutParams(new LinearLayout.LayoutParams(75, 75));
+        container.addView(myViewAlphaHandled);
+    }
+
+    private void startAnim(View target) {
+        ObjectAnimator anim = ObjectAnimator.ofFloat(target, View.ALPHA, 0);
+        anim.setRepeatCount(ValueAnimator.INFINITE);
+        anim.setRepeatMode(ValueAnimator.REVERSE);
+        anim.setDuration(1000);
+        anim.start();
+    }
+    private void startAnim(int id) {
+        startAnim(findViewById(id));
+    }
+    
+    private static class MyView extends View {
+        private int mMyAlpha = 255;
+        private boolean mHandleAlpha;
+        private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        
+        private MyView(Context context, boolean handleAlpha) {
+            super(context);
+            mHandleAlpha = handleAlpha;
+            mPaint.setColor(Color.RED);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            if (mHandleAlpha) {
+                mPaint.setAlpha(mMyAlpha);
+            }
+            canvas.drawCircle(30, 30, 30, mPaint);
+        }
+
+        @Override
+        protected boolean onSetAlpha(int alpha) {
+            if (mHandleAlpha) {
+                mMyAlpha = alpha;
+                return true;
+            }
+            return super.onSetAlpha(alpha);
+        }
+    }
+
+}