Add Shadow demos to ApiDemos

bug:15724818
Change-Id: I22e3faaee9bb1d46f9c9f16cac38069071e1fc0b
diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml
index 095d7ee..7d681cd 100644
--- a/samples/ApiDemos/AndroidManifest.xml
+++ b/samples/ApiDemos/AndroidManifest.xml
@@ -2685,6 +2685,24 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".graphics.ShadowCardStack"
+                  android:label="Graphics/Shadow Card Stack"
+                  android:theme="@android:style/Theme.Material.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".graphics.ShadowCardDrag"
+                  android:label="Graphics/Shadow Card Drag"
+                  android:theme="@android:style/Theme.Material.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
         <activity android:name=".graphics.WindowSurface"
                 android:label="Graphics/Surface Window">
             <intent-filter>
diff --git a/samples/ApiDemos/res/drawable/round_rect.xml b/samples/ApiDemos/res/drawable/round_rect.xml
new file mode 100644
index 0000000..6577d14
--- /dev/null
+++ b/samples/ApiDemos/res/drawable/round_rect.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#ffffffff" />
+    <corners android:radius="10dp" />
+</shape>
\ No newline at end of file
diff --git a/samples/ApiDemos/res/layout/shadow_card_drag.xml b/samples/ApiDemos/res/layout/shadow_card_drag.xml
new file mode 100644
index 0000000..c3bc474
--- /dev/null
+++ b/samples/ApiDemos/res/layout/shadow_card_drag.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/card_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:orientation="horizontal" >
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <CheckBox
+            android:id="@+id/tilt_check"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:checked="false"
+            android:text="@string/enable_tilt"/>
+        <CheckBox
+            android:id="@+id/shading_check"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:checked="false"
+            android:text="@string/enable_shading"/>
+        <Button
+            android:id="@+id/shape_select"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/select_shape"/>
+        </LinearLayout>
+
+    <TextView
+        android:id="@+id/card"
+        android:layout_width="150dp"
+        android:layout_height="150dp"
+        android:background="@drawable/round_rect"
+        android:clipToPadding="false"
+        android:gravity="center"
+        android:padding="20dp"
+        android:text="@string/draggable_card"
+        android:textSize="20sp"
+        android:elevation="2dp"/>
+</FrameLayout>
\ No newline at end of file
diff --git a/samples/ApiDemos/res/layout/shadow_card_stack.xml b/samples/ApiDemos/res/layout/shadow_card_stack.xml
new file mode 100644
index 0000000..11d60f7
--- /dev/null
+++ b/samples/ApiDemos/res/layout/shadow_card_stack.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/card_parent"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:gravity="center">
+    <TextView
+        android:layout_width="250dp"
+        android:layout_height="150dp"
+        android:background="@drawable/round_rect"
+        android:padding="20dp"
+        android:textSize="20sp" />
+    <TextView
+        android:layout_width="250dp"
+        android:layout_height="150dp"
+        android:background="@drawable/round_rect"
+        android:padding="20dp"
+        android:textSize="20sp" />
+    <TextView
+        android:layout_width="250dp"
+        android:layout_height="150dp"
+        android:background="@drawable/round_rect"
+        android:padding="20dp"
+        android:textSize="20sp" />
+    <TextView
+        android:layout_width="250dp"
+        android:layout_height="150dp"
+        android:background="@drawable/round_rect"
+        android:padding="20dp"
+        android:textSize="20sp" />
+    <TextView
+        android:layout_width="250dp"
+        android:layout_height="150dp"
+        android:background="@drawable/round_rect"
+        android:padding="20dp"
+        android:textSize="20sp" />
+</RelativeLayout>
\ No newline at end of file
diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml
index 46ef1a2..6a2df2e 100644
--- a/samples/ApiDemos/res/values/strings.xml
+++ b/samples/ApiDemos/res/values/strings.xml
@@ -904,6 +904,11 @@
     <string name="density_title">Density: Unknown Screen</string>
     <string name="camera_alert">Device has only one camera!</string>
     <string name="switch_cam">Switch Camera</string>
+    <string name="draggable_card">Draggable Card</string>
+
+    <string name="enable_tilt">Enable Tilt</string>
+    <string name="enable_shading">Enable Shading</string>
+    <string name="select_shape">Select Shape</string>
 
     <!-- ============================ -->
     <!--  media examples strings  -->
diff --git a/samples/ApiDemos/src/com/example/android/apis/graphics/ShadowCardDrag.java b/samples/ApiDemos/src/com/example/android/apis/graphics/ShadowCardDrag.java
new file mode 100644
index 0000000..facc009
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/graphics/ShadowCardDrag.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2014 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.example.android.apis.graphics;
+
+import android.animation.ObjectAnimator;
+import android.app.Activity;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.OvalShape;
+import android.graphics.drawable.shapes.RectShape;
+import android.graphics.drawable.shapes.RoundRectShape;
+import android.graphics.drawable.shapes.Shape;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import com.example.android.apis.R;
+
+import java.util.ArrayList;
+
+public class ShadowCardDrag extends Activity {
+    private static final float MAX_Z_DP = 10;
+    private static final float MOMENTUM_SCALE = 10;
+    private static final int MAX_ANGLE = 10;
+
+    private class CardDragState {
+        long lastEventTime;
+        float lastX;
+        float lastY;
+
+        float momentumX;
+        float momentumY;
+
+        public void onDown(long eventTime, float x, float y) {
+            lastEventTime = eventTime;
+            lastX = x;
+            lastY = y;
+
+            momentumX = 0;
+            momentumY = 0;
+        }
+
+        public void onMove(long eventTime, float x, float y) {
+            final long deltaT = eventTime - lastEventTime;
+
+            if (deltaT != 0) {
+                float newMomentumX = (x - lastX) / (mDensity * deltaT);
+                float newMomentumY = (y - lastY) / (mDensity * deltaT);
+
+                momentumX = 0.9f * momentumX + 0.1f * (newMomentumX * MOMENTUM_SCALE);
+                momentumY = 0.9f * momentumY + 0.1f * (newMomentumY * MOMENTUM_SCALE);
+
+                momentumX = Math.max(Math.min((momentumX), MAX_ANGLE), -MAX_ANGLE);
+                momentumY = Math.max(Math.min((momentumY), MAX_ANGLE), -MAX_ANGLE);
+
+                //noinspection SuspiciousNameCombination
+                mCard.setRotationX(-momentumY);
+                //noinspection SuspiciousNameCombination
+                mCard.setRotationY(momentumX);
+
+                if (mShadingEnabled) {
+                    float alphaDarkening = (momentumX * momentumX + momentumY * momentumY) / (90 * 90);
+                    alphaDarkening /= 2;
+
+                    int alphaByte = 0xff - ((int)(alphaDarkening * 255) & 0xff);
+                    int color = Color.rgb(alphaByte, alphaByte, alphaByte);
+                    mCardBackground.setColorFilter(color, PorterDuff.Mode.MULTIPLY);
+                }
+            }
+
+            lastX = x;
+            lastY = y;
+            lastEventTime = eventTime;
+        }
+
+        public void onUp() {
+            ObjectAnimator flattenX = ObjectAnimator.ofFloat(mCard, "rotationX", 0);
+            flattenX.setDuration(100);
+            flattenX.setInterpolator(new AccelerateInterpolator());
+            flattenX.start();
+
+            ObjectAnimator flattenY = ObjectAnimator.ofFloat(mCard, "rotationY", 0);
+            flattenY.setDuration(100);
+            flattenY.setInterpolator(new AccelerateInterpolator());
+            flattenY.start();
+            mCardBackground.setColorFilter(null);
+        }
+    }
+
+    /**
+     * Simple shape example that generates a shadow casting outline.
+     */
+    private static class TriangleShape extends Shape {
+        private final Path mPath = new Path();
+
+        @Override
+        protected void onResize(float width, float height) {
+            mPath.reset();
+            mPath.moveTo(0, 0);
+            mPath.lineTo(width, 0);
+            mPath.lineTo(width / 2, height);
+            mPath.lineTo(0, 0);
+            mPath.close();
+        }
+
+        @Override
+        public void draw(Canvas canvas, Paint paint) {
+            canvas.drawPath(mPath, paint);
+        }
+
+        @Override
+        public boolean getOutline(Outline outline) {
+            outline.setConvexPath(mPath);
+            return true;
+        }
+    }
+
+    private final ShapeDrawable mCardBackground = new ShapeDrawable();
+    private final ArrayList<Shape> mShapes = new ArrayList<Shape>();
+    private float mDensity;
+    private View mCard;
+
+    private final CardDragState mDragState = new CardDragState();
+    private boolean mTiltEnabled;
+    private boolean mShadingEnabled;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.shadow_card_drag);
+
+        mDensity = getResources().getDisplayMetrics().density;
+        mShapes.add(new RectShape());
+        mShapes.add(new OvalShape());
+        float r = 10 * mDensity;
+        float radii[] = new float[] {r, r, r, r, r, r, r, r};
+        mShapes.add(new RoundRectShape(radii, null, null));
+        mShapes.add(new TriangleShape());
+
+        mCardBackground.getPaint().setColor(Color.WHITE);
+        mCardBackground.setShape(mShapes.get(0));
+        final View cardParent = findViewById(R.id.card_parent);
+        mCard = findViewById(R.id.card);
+        mCard.setBackground(mCardBackground);
+
+        final CheckBox tiltCheck = (CheckBox) findViewById(R.id.tilt_check);
+        tiltCheck.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                mTiltEnabled = isChecked;
+                if (!mTiltEnabled) {
+                    mDragState.onUp();
+                }
+            }
+        });
+
+        final CheckBox shadingCheck = (CheckBox) findViewById(R.id.shading_check);
+        shadingCheck.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                mShadingEnabled = isChecked;
+                if (!mShadingEnabled) {
+                    mCardBackground.setColorFilter(null);
+                }
+            }
+        });
+
+        final Button shapeButton = (Button) findViewById(R.id.shape_select);
+        shapeButton.setOnClickListener(new View.OnClickListener() {
+            int index = 0;
+            @Override
+            public void onClick(View v) {
+                index = (index + 1) % mShapes.size();
+                mCardBackground.setShape(mShapes.get(index));
+            }
+        });
+
+        /**
+         * Enable any touch on the parent to drag the card. Note that this doesn't do a proper hit
+         * test, so any drag (including off of the card) will work.
+         *
+         * This enables the user to see the effect more clearly for the purpose of this demo.
+         */
+        cardParent.setOnTouchListener(new View.OnTouchListener() {
+            float downX;
+            float downY;
+            long downTime;
+
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                switch (event.getAction()) {
+                    case MotionEvent.ACTION_DOWN:
+                        downX = event.getX() - mCard.getTranslationX();
+                        downY = event.getY() - mCard.getTranslationY();
+                        downTime = event.getDownTime();
+                        ObjectAnimator upAnim = ObjectAnimator.ofFloat(mCard, "translationZ",
+                                MAX_Z_DP * mDensity);
+                        upAnim.setDuration(100);
+                        upAnim.setInterpolator(new DecelerateInterpolator());
+                        upAnim.start();
+                        if (mTiltEnabled) {
+                            mDragState.onDown(event.getDownTime(), event.getX(), event.getY());
+                        }
+                        break;
+                    case MotionEvent.ACTION_MOVE:
+                        mCard.setTranslationX(event.getX() - downX);
+                        mCard.setTranslationY(event.getY() - downY);
+                        if (mTiltEnabled) {
+                            mDragState.onMove(event.getEventTime(), event.getX(), event.getY());
+                        }
+                        break;
+                    case MotionEvent.ACTION_UP:
+                        ObjectAnimator downAnim = ObjectAnimator.ofFloat(mCard, "translationZ", 0);
+                        downAnim.setDuration(100);
+                        downAnim.setInterpolator(new AccelerateInterpolator());
+                        downAnim.start();
+                        if (mTiltEnabled) {
+                            mDragState.onUp();
+                        }
+                        break;
+                }
+                return true;
+            }
+        });
+    }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/graphics/ShadowCardStack.java b/samples/ApiDemos/src/com/example/android/apis/graphics/ShadowCardStack.java
new file mode 100644
index 0000000..5a34251
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/graphics/ShadowCardStack.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2014 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.example.android.apis.graphics;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.example.android.apis.R;
+
+import java.util.ArrayList;
+
+public class ShadowCardStack extends Activity {
+
+    private static final float X_SHIFT_DP = 1000;
+    private static final float Y_SHIFT_DP = 50;
+    private static final float Z_LIFT_DP = 8;
+    private static final float ROTATE_DEGREES = 15;
+
+    public AnimatorSet createSet(ArrayList<Animator> items, long startDelay) {
+        AnimatorSet set = new AnimatorSet();
+        set.playTogether(items);
+        set.setStartDelay(startDelay);
+        return set;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.shadow_card_stack);
+
+        float density = getResources().getDisplayMetrics().density;
+
+        final ViewGroup cardParent = (ViewGroup) findViewById(R.id.card_parent);
+
+        final float X = X_SHIFT_DP * density;
+        final float Y = Y_SHIFT_DP * density;
+        final float Z = Z_LIFT_DP * density;
+
+        ArrayList<Animator> towardAnimators = new ArrayList<Animator>();
+        ArrayList<Animator> expandAnimators = new ArrayList<Animator>();
+        ArrayList<Animator> moveAwayAnimators = new ArrayList<Animator>();
+        ArrayList<Animator> moveBackAnimators = new ArrayList<Animator>();
+        ArrayList<Animator> awayAnimators = new ArrayList<Animator>();
+        ArrayList<Animator> collapseAnimators = new ArrayList<Animator>();
+
+        final int max = cardParent.getChildCount();
+        for (int i = 0; i < max; i++) {
+            TextView card = (TextView) cardParent.getChildAt(i);
+            card.setText("Card number " + i);
+
+            float targetY = (i - (max-1) / 2.0f) * Y;
+            Animator expand = ObjectAnimator.ofFloat(card, "translationY", targetY);
+            expandAnimators.add(expand);
+
+            Animator toward = ObjectAnimator.ofFloat(card, "translationZ", i * Z);
+            toward.setStartDelay(200 * ((max) - i));
+            towardAnimators.add(toward);
+
+            card.setPivotX(X_SHIFT_DP);
+            Animator rotateAway = ObjectAnimator.ofFloat(card, "rotationY",
+                    i == 0 ? 0 : ROTATE_DEGREES);
+            rotateAway.setStartDelay(200 * ((max) - i));
+            rotateAway.setDuration(100);
+            moveAwayAnimators.add(rotateAway);
+            Animator slideAway = ObjectAnimator.ofFloat(card, "translationX",
+                    i == 0 ? 0 : X);
+            slideAway.setStartDelay(200 * ((max) - i));
+            slideAway.setDuration(100);
+            moveAwayAnimators.add(slideAway);
+
+            Animator rotateBack = ObjectAnimator.ofFloat(card, "rotationY", 0);
+            rotateBack.setStartDelay(200 * i);
+            moveBackAnimators.add(rotateBack);
+            Animator slideBack = ObjectAnimator.ofFloat(card, "translationX", 0);
+            slideBack.setStartDelay(200 * i);
+            moveBackAnimators.add(slideBack);
+
+            Animator away = ObjectAnimator.ofFloat(card, "translationZ", 0);
+            away.setStartDelay(200 * i);
+            awayAnimators.add(away);
+
+            Animator collapse = ObjectAnimator.ofFloat(card, "translationY", 0);
+            collapseAnimators.add(collapse);
+        }
+
+        AnimatorSet totalSet = new AnimatorSet();
+        totalSet.playSequentially(
+                createSet(expandAnimators, 250),
+                createSet(towardAnimators, 0),
+
+                createSet(moveAwayAnimators, 250),
+                createSet(moveBackAnimators, 0),
+
+                createSet(awayAnimators, 250),
+                createSet(collapseAnimators, 0));
+        totalSet.start();
+        totalSet.addListener(new RepeatListener(totalSet));
+    }
+
+    public static class RepeatListener implements Animator.AnimatorListener {
+        final Animator mRepeatAnimator;
+        public RepeatListener(Animator repeatAnimator) {
+            mRepeatAnimator = repeatAnimator;
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation) {}
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (animation == mRepeatAnimator) {
+                mRepeatAnimator.start();
+            }
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {}
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {}
+    }
+}