Add support for the Z coordinate in GLCanvas and thus a new set of animations
to support this.

Change-Id: I652bbe48082a17bce4dca86217dc1351edd74f13
diff --git a/new3d/src/com/android/gallery3d/anim/AlphaAnimation.java b/new3d/src/com/android/gallery3d/anim/AlphaAnimation.java
new file mode 100644
index 0000000..d806885
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/anim/AlphaAnimation.java
@@ -0,0 +1,34 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+
+package com.android.gallery3d.anim;
+
+import com.android.gallery3d.ui.GLCanvas;
+import com.android.gallery3d.ui.Util;
+
+public class AlphaAnimation extends CanvasAnimation {
+    private final float mStartAlpha;
+    private final float mEndAlpha;
+    private float mCurrentAlpha;
+
+    public AlphaAnimation(float from, float to) {
+        mStartAlpha = from;
+        mEndAlpha = to;
+        mCurrentAlpha = from;
+    }
+
+    @Override
+    public void apply(GLCanvas canvas) {
+        canvas.multiplyAlpha(mCurrentAlpha);
+    }
+
+    @Override
+    public int getCanvasSaveFlags() {
+        return GLCanvas.ALPHA_SAVE_FLAG;
+    }
+
+    @Override
+    protected void onCalculate(float progress) {
+        mCurrentAlpha = Util.clamp(mStartAlpha
+                + (mEndAlpha - mStartAlpha) * progress, 0f, 1f);
+    }
+}
diff --git a/new3d/src/com/android/gallery3d/anim/Animation.java b/new3d/src/com/android/gallery3d/anim/Animation.java
index 9cbef24..be9f5b7 100644
--- a/new3d/src/com/android/gallery3d/anim/Animation.java
+++ b/new3d/src/com/android/gallery3d/anim/Animation.java
@@ -49,6 +49,10 @@
         mStartTime = ANIMATION_START;
     }
 
+    public void setStartTime(long time) {
+        mStartTime = time;
+    }
+
     public boolean isActive() {
         return mStartTime != NO_ANIMATION;
     }
@@ -58,11 +62,16 @@
         if (mStartTime == ANIMATION_START) mStartTime = currentTimeMillis;
         int elapse = (int) (currentTimeMillis - mStartTime);
         float x = Util.clamp((float) elapse / mDuration, 0f, 1f);
-        if (mInterpolator != null) x = mInterpolator.getInterpolation(x);
-        if (onCalculate(x)) return true;
-        mStartTime = NO_ANIMATION;
-        return false;
+        onCalculate(mInterpolator != null
+                ? mInterpolator.getInterpolation(x)
+                : x);
+        if (elapse >= mDuration) {
+            mStartTime = NO_ANIMATION;
+            return false;
+        } else {
+            return true;
+        }
     }
 
-    abstract protected boolean onCalculate(float progress);
+    abstract protected void onCalculate(float progress);
 }
diff --git a/new3d/src/com/android/gallery3d/anim/AnimationSet.java b/new3d/src/com/android/gallery3d/anim/AnimationSet.java
new file mode 100644
index 0000000..b4d2e99
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/anim/AnimationSet.java
@@ -0,0 +1,62 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+
+package com.android.gallery3d.anim;
+
+import com.android.gallery3d.ui.GLCanvas;
+
+import java.util.ArrayList;
+
+public class AnimationSet extends CanvasAnimation {
+
+    private final ArrayList<CanvasAnimation> mAnimations =
+            new ArrayList<CanvasAnimation>();
+    private int mSaveFlags = 0;
+
+
+    public void addAnimation(CanvasAnimation anim) {
+        mAnimations.add(anim);
+        mSaveFlags |= anim.getCanvasSaveFlags();
+    }
+
+    @Override
+    public void apply(GLCanvas canvas) {
+        for (CanvasAnimation anim : mAnimations) {
+            anim.apply(canvas);
+        }
+    }
+
+    @Override
+    public int getCanvasSaveFlags() {
+        return mSaveFlags;
+    }
+
+    @Override
+    protected void onCalculate(float progress) {
+        // DO NOTHING
+    }
+
+    @Override
+    public boolean calculate(long currentTimeMillis) {
+        boolean more = false;
+        for (CanvasAnimation anim : mAnimations) {
+            more |= anim.calculate(currentTimeMillis);
+        }
+        return more;
+    }
+
+    @Override
+    public void start() {
+        for (CanvasAnimation anim : mAnimations) {
+            anim.start();
+        }
+    }
+
+    @Override
+    public boolean isActive() {
+        for (CanvasAnimation anim : mAnimations) {
+            if (anim.isActive()) return true;
+        }
+        return false;
+    }
+
+}
diff --git a/new3d/src/com/android/gallery3d/anim/CanvasAnimation.java b/new3d/src/com/android/gallery3d/anim/CanvasAnimation.java
new file mode 100644
index 0000000..de24ae4
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/anim/CanvasAnimation.java
@@ -0,0 +1,11 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+
+package com.android.gallery3d.anim;
+
+import com.android.gallery3d.ui.GLCanvas;
+
+public abstract class CanvasAnimation extends Animation {
+
+    public abstract int getCanvasSaveFlags();
+    public abstract void apply(GLCanvas canvas);
+}
diff --git a/new3d/src/com/android/gallery3d/anim/FloatAnimation.java b/new3d/src/com/android/gallery3d/anim/FloatAnimation.java
index 7b3a2db..084383f 100644
--- a/new3d/src/com/android/gallery3d/anim/FloatAnimation.java
+++ b/new3d/src/com/android/gallery3d/anim/FloatAnimation.java
@@ -13,14 +13,8 @@
     }
 
     @Override
-    protected boolean onCalculate(float progress) {
-        if (progress < 1f) {
-            mCurrent = mFrom + (mTo - mFrom) * progress;
-            return true;
-        } else {
-            mCurrent = mTo;
-            return false;
-        }
+    protected void onCalculate(float progress) {
+        mCurrent = mFrom + (mTo - mFrom) * progress;
     }
 
     public float get() {
diff --git a/new3d/src/com/android/gallery3d/anim/IntAnimation.java b/new3d/src/com/android/gallery3d/anim/IntAnimation.java
index 2fdeb5d..9bdd5fe 100644
--- a/new3d/src/com/android/gallery3d/anim/IntAnimation.java
+++ b/new3d/src/com/android/gallery3d/anim/IntAnimation.java
@@ -13,14 +13,8 @@
     }
 
     @Override
-    protected boolean onCalculate(float progress) {
-        if (progress < 1f) {
-            mCurrent = mFrom + (int)((mTo - mFrom) * progress + .5f);
-            return true;
-        } else {
-            mCurrent = mTo;
-            return false;
-        }
+    protected void onCalculate(float progress) {
+        mCurrent = mFrom + (int)((mTo - mFrom) * progress + .5f);
     }
 
     public int get() {
diff --git a/new3d/src/com/android/gallery3d/anim/ScaleAnimation.java b/new3d/src/com/android/gallery3d/anim/ScaleAnimation.java
new file mode 100644
index 0000000..f5dca50
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/anim/ScaleAnimation.java
@@ -0,0 +1,63 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+
+package com.android.gallery3d.anim;
+
+import com.android.gallery3d.ui.GLCanvas;
+
+public class ScaleAnimation extends CanvasAnimation {
+    private final float mFromX;
+    private final float mFromY;
+    private final float mFromZ;
+    private final float mToX;
+    private final float mToY;
+    private final float mToZ;
+
+    private float mCurrentX;
+    private float mCurrentY;
+    private float mCurrentZ;
+
+    private final float mPivotX;
+    private final float mPivotY;
+    private final float mPivotZ;
+
+    public ScaleAnimation(float fromX, float toX,
+            float fromY, float toY, float pivotX, float pivotY) {
+        this(fromX, toX, fromY, toY, 0, 0, pivotX, pivotY, 0);
+    }
+
+    public ScaleAnimation(float fromX, float toX, float fromY, float toY,
+            float fromZ, float toZ, float px, float py, float pz) {
+        mFromX = fromX;
+        mFromY = fromY;
+        mFromZ = fromZ;
+        mToX = toX;
+        mToY = toY;
+        mToZ = toZ;
+        mPivotX = px;
+        mPivotY = py;
+        mPivotZ = pz;
+    }
+
+    @Override
+    public void apply(GLCanvas canvas) {
+        if (mPivotX != 0 || mPivotY != 0 || mPivotZ != 0)  {
+            canvas.translate(mPivotX, mPivotY, mPivotZ);
+            canvas.scale(mCurrentX, mCurrentY, mCurrentZ);
+            canvas.translate(-mPivotX, -mPivotY, -mPivotZ);
+        } else {
+            canvas.scale(mCurrentX, mCurrentY, mCurrentZ);
+        }
+    }
+
+    @Override
+    public int getCanvasSaveFlags() {
+        return GLCanvas.MATRIX_SAVE_FLAG;
+    }
+
+    @Override
+    protected void onCalculate(float progress) {
+        mCurrentX = mFromX + (mToX - mFromX) * progress;
+        mCurrentY = mFromY + (mToY - mFromY) * progress;
+        mCurrentZ = mFromZ + (mToZ - mFromZ) * progress;
+    }
+}
diff --git a/new3d/src/com/android/gallery3d/ui/DisplayItem.java b/new3d/src/com/android/gallery3d/ui/DisplayItem.java
index 2d97804..1174e5b 100644
--- a/new3d/src/com/android/gallery3d/ui/DisplayItem.java
+++ b/new3d/src/com/android/gallery3d/ui/DisplayItem.java
@@ -1,6 +1,5 @@
 package com.android.gallery3d.ui;
 
-import android.graphics.Matrix;
 
 
 public abstract class DisplayItem {
@@ -22,14 +21,9 @@
             mTheata = p.mTheata;
         }
 
-        public void apply(Matrix matrix) {
-            matrix.preTranslate(mX, mY);
-            matrix.preRotate(mTheata);
-        }
-
-        public void inverse(Matrix matrix) {
-            matrix.preRotate(-mTheata);
-            matrix.preTranslate(-mX, -mY);
+        public void apply(GLCanvas canvas) {
+            canvas.translate(mX, mY, 0);
+            canvas.rotate(mTheata, 0, 0, 1);
         }
     }
 
diff --git a/new3d/src/com/android/gallery3d/ui/DisplayItemPanel.java b/new3d/src/com/android/gallery3d/ui/DisplayItemPanel.java
index 41bf59c..ba6ec37 100644
--- a/new3d/src/com/android/gallery3d/ui/DisplayItemPanel.java
+++ b/new3d/src/com/android/gallery3d/ui/DisplayItemPanel.java
@@ -1,9 +1,7 @@
 package com.android.gallery3d.ui;
 
-import android.graphics.Matrix;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
-import android.view.animation.Transformation;
 
 import java.util.ArrayList;
 
@@ -89,9 +87,7 @@
 
     @Override
     protected void render(GLCanvas canvas) {
-        Transformation transform = canvas.getTransformation();
-        Matrix matrix = transform.getMatrix();
-        matrix.preTranslate(-mScrollX, 0);
+        canvas.translate(-mScrollX, 0, 0);
         if (mAnimationStartTime == NO_ANIMATION) {
             for (DisplayItem item: mItems) {
                 renderItem(canvas, item);
@@ -114,35 +110,32 @@
                 invalidate();
             }
         }
-        matrix.preTranslate(mScrollX, 0);
+        canvas.translate(mScrollX, 0, 0);
     }
 
     private void renderItem(GLCanvas canvas, DisplayItem item) {
-        Transformation transformation = canvas.pushTransform();
-        item.mCurrent.apply(transformation.getMatrix());
+        canvas.save(GLCanvas.ALPHA_SAVE_FLAG | GLCanvas.MATRIX_SAVE_FLAG);
+        item.mCurrent.apply(canvas);
         item.render(canvas);
-        canvas.popTransform();
+        canvas.restore();
     }
 
     private void renderItem(
             GLCanvas canvas, DisplayItem item, float interpolate) {
-        Transformation transform = canvas.getTransformation();
-        float alpha = transform.getAlpha();
-        Matrix matrix = transform.getMatrix();
+        canvas.save(GLCanvas.ALPHA_SAVE_FLAG | GLCanvas.MATRIX_SAVE_FLAG);
         switch (item.mState) {
             case STATE_MOVED:
                 item.updateCurrentPosition(interpolate);
                 break;
             case STATE_NEWBIE:
-                transform.setAlpha(alpha * interpolate);
+                canvas.multiplyAlpha(interpolate);
                 break;
             case STATE_REMOVED:
-                transform.setAlpha(alpha * (1.0f - interpolate));
+                canvas.multiplyAlpha(1.0f - interpolate);
                 break;
         }
-        item.mCurrent.apply(matrix);
+        item.mCurrent.apply(canvas);
         item.render(canvas);
-        item.mCurrent.inverse(matrix);
-        transform.setAlpha(alpha);
+        canvas.restore();
     }
 }
diff --git a/new3d/src/com/android/gallery3d/ui/GLCanvas.java b/new3d/src/com/android/gallery3d/ui/GLCanvas.java
index 448b31d..770cac4 100644
--- a/new3d/src/com/android/gallery3d/ui/GLCanvas.java
+++ b/new3d/src/com/android/gallery3d/ui/GLCanvas.java
@@ -1,10 +1,9 @@
 package com.android.gallery3d.ui;
 
-import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.opengl.GLU;
-import android.view.animation.Transformation;
+import android.opengl.Matrix;
 
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -17,6 +16,12 @@
 import javax.microedition.khronos.opengles.GL11Ext;
 
 public class GLCanvas {
+
+    public static final int ALL_SAVE_FLAG = 0xFFFFFFFF;
+    public static final int CLIP_SAVE_FLAG = 0x01;
+    public static final int ALPHA_SAVE_FLAG = 0x02;
+    public static final int MATRIX_SAVE_FLAG = 0x04;
+
     // We need 16 vertices for a normal nine-patch image (the 4x4 vertices)
     private static final int VERTEX_BUFFER_SIZE = 16 * 2;
 
@@ -27,13 +32,6 @@
 
     private final GL11 mGL;
 
-    private final Stack<Transformation> mFreeTransform =
-            new Stack<Transformation>();
-
-    private final Transformation mTransformation = new Transformation();
-    private final Stack<Transformation> mTransformStack =
-            new Stack<Transformation>();
-
     private final float mMatrixValues[] = new float[16];
 
     private final float mUvBuffer[] = new float[VERTEX_BUFFER_SIZE];
@@ -54,8 +52,11 @@
 
     private long mAnimationTime;
 
-    private int mWidth;
-    private int mHeight;
+    private float mAlpha;
+    private final Rect mClipRect = new Rect();
+    private final Stack<ConfigState> mRestoreStack =
+            new Stack<ConfigState>();
+    private ConfigState mRecycledRestoreAction;
 
     GLCanvas(GL11 gl) {
         mGL = gl;
@@ -64,53 +65,41 @@
     }
 
     public void setSize(int width, int height) {
-        mWidth = width;
-        mHeight = height;
         GL11 gl = mGL;
-
         gl.glViewport(0, 0, width, height);
         gl.glMatrixMode(GL11.GL_PROJECTION);
         gl.glLoadIdentity();
         GLU.gluOrtho2D(gl, 0, width, 0, height);
 
-        Matrix matrix = mTransformation.getMatrix();
-        matrix.reset();
-        matrix.preTranslate(0, mHeight);
-        matrix.preScale(1, -1);
+        gl.glMatrixMode(GL11.GL_MODELVIEW);
+        gl.glLoadIdentity();
+
+        // The positive direction in Y coordinate in OpenGL is from bottom to
+        // top, which is different from the coordinate system in Java. So, we
+        // flip it here.
+        float matrix[] = mMatrixValues;
+        Matrix.setIdentityM(matrix, 0);
+        Matrix.translateM(matrix, 0, 0, height, 0);
+        Matrix.scaleM(matrix, 0, 1, -1, 1);
+
+        mClipRect.set(0, 0, width, height);
+        gl.glScissor(0, 0, width, height);
     }
 
     public long currentAnimationTimeMillis() {
         return mAnimationTime;
     }
 
-    public Transformation obtainTransformation() {
-        if (!mFreeTransform.isEmpty()) {
-            Transformation t = mFreeTransform.pop();
-            t.clear();
-            return t;
-        }
-        return new Transformation();
+    public void setAlpha(float alpha) {
+        mAlpha = alpha;
     }
 
-    public void freeTransformation(Transformation freeTransformation) {
-        mFreeTransform.push(freeTransformation);
+    public void multiplyAlpha(float alpha) {
+        mAlpha *= alpha;
     }
 
-    public Transformation getTransformation() {
-        return mTransformation;
-    }
-
-    public Transformation pushTransform() {
-        Transformation trans = obtainTransformation();
-        trans.set(mTransformation);
-        mTransformStack.push(trans);
-        return mTransformation;
-    }
-
-    public void popTransform() {
-        Transformation trans = mTransformStack.pop();
-        mTransformation.set(trans);
-        freeTransformation(trans);
+    public float getAlpha() {
+        return mAlpha;
     }
 
     private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
@@ -133,6 +122,8 @@
         gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, mUvPointer);
         gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
 
+        // mMatrixValues will be initialized in setSize()
+        mAlpha = 1.0f;
     }
 
     private static void putRectangle(float x, float y,
@@ -149,17 +140,14 @@
     }
 
     public void setColor(int color) {
-        float alpha = mTransformation.getAlpha();
+        float alpha = mAlpha;
         mGLState.setBlendEnabled(!Util.isOpaque(color) || alpha < OPAQUE_ALPHA);
         mGLState.setFragmentColor(color, alpha);
     }
 
     public void drawLine(int x1, int y1, int x2, int y2) {
-        float matrix[] = mMatrixValues;
-        mTransformation.getMatrix().getValues(matrix);
         GL11 gl = mGL;
-        gl.glPushMatrix();
-        gl.glMultMatrixf(toGLMatrix(matrix), 0);
+        gl.glLoadMatrixf(mMatrixValues, 0);
         float buffer[] = mXyBuffer;
         buffer[0] = x1;
         buffer[1] = y1;
@@ -167,32 +155,34 @@
         buffer[3] = y2;
         mXyPointer.put(buffer, 0, 4).position(0);
         gl.glDrawArrays(GL11.GL_LINE_STRIP, 0, 2);
-        gl.glPopMatrix();
     }
 
     public void fillRect(Rect r) {
         fillRect(r.left, r.top, r.right - r.left, r.bottom - r.top);
     }
 
-    public void fillRect(int x, int y, int width, int height) {
-        float matrix[] = mMatrixValues;
-        mTransformation.getMatrix().getValues(matrix);
-        fillRect(x, y, width, height, matrix);
+    public void translate(float x, float y, float z) {
+        Matrix.translateM(mMatrixValues, 0, x, y, z);
     }
 
-    private void fillRect(
-            int x, int y, int width, int height, float matrix[]) {
+    public void scale(float sx, float sy, float sz) {
+        Matrix.scaleM(mMatrixValues, 0, sx, sy, sz);
+    }
+
+    public void rotate(float angle, float x, float y, float z) {
+        Matrix.rotateM(mMatrixValues, 0, angle, x, y, z);
+    }
+
+    public void fillRect(int x, int y, int width, int height) {
         GL11 gl = mGL;
-        gl.glPushMatrix();
-        gl.glMultMatrixf(toGLMatrix(matrix), 0);
+        gl.glLoadMatrixf(mMatrixValues, 0);
         putRectangle(x, y, width, height, mXyBuffer, mXyPointer);
         gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, 0, 4);
-        gl.glPopMatrix();
     }
 
     public void drawNinePatch(
             NinePatchTexture tex, int x, int y, int width, int height) {
-        float alpha = mTransformation.getAlpha();
+        float alpha = mAlpha;
         NinePatchChunk chunk = tex.getNinePatchChunk();
 
         mGLState.setTexture2DEnabled(true);
@@ -216,14 +206,11 @@
         mGLState.setBlendEnabled(!tex.isOpaque() || alpha < OPAQUE_ALPHA);
 
         mGLState.setTextureAlpha(alpha);
-        Matrix matrix = mTransformation.getMatrix();
-        matrix.getValues(mMatrixValues);
+
         GL11 gl = mGL;
-        gl.glPushMatrix();
-        gl.glMultMatrixf(toGLMatrix(mMatrixValues), 0);
+        gl.glLoadMatrixf(mMatrixValues, 0);
         gl.glTranslatef(x, y, 0);
         drawMesh(divX, divY, divU, divV, nx, ny);
-        gl.glPopMatrix();
     }
 
     /**
@@ -368,50 +355,60 @@
                 idxCount, GL11.GL_UNSIGNED_BYTE, mIndexPointer);
     }
 
-    private float[] mapPoints(Matrix matrix, int x1, int y1, int x2, int y2) {
+    private float[] mapPoints(float matrix[], int x1, int y1, int x2, int y2) {
         float[] point = mXyBuffer;
-        point[0] = x1; point[1] = y1; point[2] = x2; point[3] = y2;
-        matrix.mapPoints(point, 0, point, 0, 4);
+        int srcOffset = 6;
+        point[srcOffset] = x1;
+        point[srcOffset + 1] = y1;
+        point[srcOffset + 2] = 0;
+        point[srcOffset + 3] = 1;
+
+        int resultOffset = 0;
+        Matrix.multiplyMV(point, resultOffset, matrix, 0, point, srcOffset);
+        point[resultOffset] /= point[resultOffset + 3];
+        point[resultOffset + 1] /= point[resultOffset + 3];
+
+        // map the second point
+        point[srcOffset] = x2;
+        point[srcOffset + 1] = y2;
+        resultOffset = 2;
+        Matrix.multiplyMV(point, resultOffset, matrix, 0, point, srcOffset);
+        point[resultOffset] /= point[resultOffset + 3];
+        point[resultOffset + 1] /= point[resultOffset + 3];
+
         return point;
     }
 
-    public void clipRect(int x, int y, int width, int height) {
-        float point[] = mapPoints(
-                mTransformation.getMatrix(), x, y + height, x + width, y);
+
+    public boolean clipRect(int left, int top, int right, int bottom) {
+        float point[] = mapPoints(mMatrixValues, left, top, right, bottom);
 
         // mMatrix could be a rotation matrix. In this case, we need to find
         // the boundaries after rotation. (only handle 90 * n degrees)
         if (point[0] > point[2]) {
-            x = (int) point[2];
-            width = (int) point[0] - x;
+            left = (int) point[2];
+            right = (int) point[0];
         } else {
-            x = (int) point[0];
-            width = (int) point[2] - x;
+            left = (int) point[0];
+            right = (int) point[2];
         }
         if (point[1] > point[3]) {
-            y = (int) point[3];
-            height = (int) point[1] - y;
+            top = (int) point[3];
+            bottom = (int) point[1];
         } else {
-            y = (int) point[1];
-            height = (int) point[3] - y;
+            top = (int) point[1];
+            bottom = (int) point[3];
         }
-        mGL.glScissor(x, y, width, height);
-    }
+        Rect clip = mClipRect;
 
-    public void clearClip() {
-        mGL.glScissor(0, 0, mWidth, mHeight);
-    }
-
-    private static float[] toGLMatrix(float v[]) {
-        v[15] = v[8]; v[13] = v[5]; v[5] = v[4]; v[4] = v[1];
-        v[12] = v[2]; v[1] = v[3]; v[3] = v[6];
-        v[2] = v[6] = v[8] = v[9] = 0;
-        v[10] = 1;
-        return v;
+        boolean intersect = clip.intersect(left, top, right, bottom);
+        if (!intersect) clip.set(0, 0, 0, 0);
+        mGL.glScissor(clip.left, clip.top, clip.width(), clip.height());
+        return intersect;
     }
 
     public void drawColor(int x, int y, int width, int height, int color) {
-        float alpha = mTransformation.getAlpha();
+        float alpha = mAlpha;
         mGLState.setBlendEnabled(!Util.isOpaque(color) || alpha < OPAQUE_ALPHA);
         mGLState.setFragmentColor(color, alpha);
         fillRect(x, y, width, height);
@@ -419,9 +416,6 @@
 
     private void drawBoundTexture(
             BasicTexture texture, int x, int y, int width, int height) {
-        Matrix matrix = mTransformation.getMatrix();
-        matrix.getValues(mMatrixValues);
-
         // Test whether it has been rotated or flipped, if so, glDrawTexiOES
         // won't work
         if (isMatrixRotatedOrFlipped(mMatrixValues)) {
@@ -429,10 +423,11 @@
                     (texture.mWidth - 0.5f) / texture.mTextureWidth,
                     (texture.mHeight - 0.5f) / texture.mTextureHeight,
                     mUvBuffer, mUvPointer);
-            fillRect(x, y, width, height, mMatrixValues);
+            fillRect(x, y, width, height);
         } else {
             // draw the rect from bottom-left to top-right
-            float points[] = mapPoints(matrix, x, y + height, x + width, y);
+            float points[] = mapPoints(
+                    mMatrixValues, x, y + height, x + width, y);
             x = (int) points[0];
             y = (int) points[1];
             width = (int) points[2] - x;
@@ -446,7 +441,7 @@
 
     public void drawTexture(
             BasicTexture texture, int x, int y, int width, int height) {
-        drawTexture(texture, x, y, width, height, mTransformation.getAlpha());
+        drawTexture(texture, x, y, width, height, mAlpha);
     }
 
     public void drawTexture(BasicTexture texture,
@@ -462,7 +457,7 @@
 
     public void drawMixed(BasicTexture from, BasicTexture to,
             float ratio, int x, int y, int w, int h) {
-        drawMixed(from, to, ratio, x, y, w, h, mTransformation.getAlpha());
+        drawMixed(from, to, ratio, x, y, w, h, mAlpha);
     }
 
     private void setTextureColor(float r, float g, float b, float alpha) {
@@ -519,20 +514,24 @@
         gl.glActiveTexture(GL11.GL_TEXTURE0);
     }
 
+    // TODO: the code only work for 2D should get fixed for 3D or removed
+    private static final int MSKEW_X = 4;
+    private static final int MSKEW_Y = 1;
+    private static final int MSCALE_X = 0;
+    private static final int MSCALE_Y = 5;
+
     private static boolean isMatrixRotatedOrFlipped(float matrix[]) {
-        return matrix[Matrix.MSKEW_X] != 0 || matrix[Matrix.MSKEW_Y] != 0
-                || matrix[Matrix.MSCALE_X] < 0 || matrix[Matrix.MSCALE_Y] > 0;
+        return matrix[MSKEW_X] != 0 || matrix[MSKEW_Y] != 0
+                || matrix[MSCALE_X] < 0 || matrix[MSCALE_Y] > 0;
     }
 
     public void copyTexture2D(
             RawTexture texture, int x, int y, int width, int height) {
-        Matrix matrix = mTransformation.getMatrix();
-        matrix.getValues(mMatrixValues);
 
         if (isMatrixRotatedOrFlipped(mMatrixValues)) {
             throw new IllegalArgumentException("cannot support rotated matrix");
         }
-        float points[] = mapPoints(matrix, x, y + height, x + width, y);
+        float points[] = mapPoints(mMatrixValues, x, y + height, x + width, y);
         x = (int) points[0];
         y = (int) points[1];
         width = (int) points[2] - x;
@@ -696,4 +695,80 @@
             mGL.glDeleteTextures(array.size(), array.toArray(null), 0);
         }
     }
+
+    public int save() {
+        return save(ALL_SAVE_FLAG);
+    }
+
+    public int save(int saveFlags) {
+        ConfigState config = obtainRestoreConfig();
+
+        if ((saveFlags & ALPHA_SAVE_FLAG) != 0) {
+            config.mAlpha = mAlpha;
+        } else {
+            config.mAlpha = -1;
+        }
+
+        if ((saveFlags & CLIP_SAVE_FLAG) != 0) {
+            config.mRect.set(mClipRect);
+        } else {
+            config.mRect.left = Integer.MAX_VALUE;
+        }
+
+        if ((saveFlags & MATRIX_SAVE_FLAG) != 0) {
+            System.arraycopy(mMatrixValues, 0, config.mMatrix, 0, 16);
+        } else {
+            config.mMatrix[0] = Float.NEGATIVE_INFINITY;
+        }
+
+        mRestoreStack.push(config);
+        return mRestoreStack.size() - 1;
+    }
+
+    public void restore() {
+        if (mRestoreStack.isEmpty()) throw new IllegalStateException();
+        ConfigState config = mRestoreStack.pop();
+        config.restore(this);
+        freeRestoreConfig(config);
+    }
+
+    public void restoreToCount(int saveCount) {
+        while (mRestoreStack.size() > saveCount) {
+            restore();
+        }
+    }
+
+    private void freeRestoreConfig(ConfigState action) {
+        action.mNextFree = mRecycledRestoreAction;
+        mRecycledRestoreAction = action;
+    }
+
+    private ConfigState obtainRestoreConfig() {
+        if (mRecycledRestoreAction != null) {
+            ConfigState result = mRecycledRestoreAction;
+            mRecycledRestoreAction = result.mNextFree;
+            return result;
+        }
+        return new ConfigState();
+    }
+
+    private static class ConfigState {
+        float mAlpha;
+        Rect mRect = new Rect();
+        float mMatrix[] = new float[16];
+        ConfigState mNextFree;
+
+        public void restore(GLCanvas canvas) {
+            if (mAlpha >= 0) canvas.setAlpha(mAlpha);
+            if (mRect.left != Integer.MAX_VALUE) {
+                Rect rect = mRect;
+                canvas.mClipRect.set(rect);
+                canvas.mGL.glScissor(
+                        rect.left, rect.top, rect.width(), rect.height());
+            }
+            if (mMatrix[0] != Float.NEGATIVE_INFINITY) {
+                System.arraycopy(mMatrix, 0, canvas.mMatrixValues, 0, 16);
+            }
+        }
+    }
 }
diff --git a/new3d/src/com/android/gallery3d/ui/GLListView.java b/new3d/src/com/android/gallery3d/ui/GLListView.java
index 5d68b13..b2042c5 100644
--- a/new3d/src/com/android/gallery3d/ui/GLListView.java
+++ b/new3d/src/com/android/gallery3d/ui/GLListView.java
@@ -1,7 +1,6 @@
 package com.android.gallery3d.ui;
 
 import static android.view.View.MeasureSpec.makeMeasureSpec;
-
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Handler;
@@ -9,11 +8,11 @@
 import android.view.GestureDetector;
 import android.view.MotionEvent;
 import android.view.View.MeasureSpec;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.Transformation;
 import android.widget.Scroller;
 
+import com.android.gallery3d.anim.AlphaAnimation;
+import com.android.gallery3d.anim.CanvasAnimation;
+
 public class GLListView extends GLView {
     @SuppressWarnings("unused")
     private static final String TAG = "GLListView";
@@ -37,7 +36,7 @@
     private boolean mHasMeasured = false;
 
     private boolean mScrollBarVisible = false;
-    private Animation mScrollBarAnimation;
+    private CanvasAnimation mScrollBarAnimation;
     private OnItemSelectedListener mOnItemSelectedListener;
 
     private final GestureDetector mGestureDetector;
@@ -120,20 +119,20 @@
     }
 
     private boolean drawWithAnimation(GLCanvas canvas,
-            Texture texture, int x, int y, int w, int h, Animation anim) {
+            Texture texture, int x, int y, int w, int h, CanvasAnimation anim) {
         long now = canvas.currentAnimationTimeMillis();
-        Transformation temp = canvas.obtainTransformation();
-        boolean more = anim.getTransformation(now, temp);
-        Transformation transformation = canvas.pushTransform();
-        transformation.compose(temp);
+        canvas.save(anim.getCanvasSaveFlags());
+        boolean more = anim.calculate(canvas.currentAnimationTimeMillis());
+        anim.apply(canvas);
         texture.draw(canvas, x, y, w, h);
-        invalidate();
-        canvas.popTransform();
+        canvas.restore();
+        if (more) invalidate();
         return more;
     }
 
     @Override
     protected void render(GLCanvas canvas) {
+        canvas.save(GLCanvas.CLIP_SAVE_FLAG);
         canvas.clipRect(0, 0, getWidth(), getHeight());
         if (mHighlightIndex != INDEX_NONE) {
             GLView view = mModel.getView(mHighlightIndex);
@@ -147,7 +146,7 @@
             }
         }
         super.render(canvas);
-        canvas.clearClip();
+        canvas.restore();
 
         if (mScrollBarAnimation != null || mScrollBarVisible) {
             int width = mScrollbar.getWidth();
diff --git a/new3d/src/com/android/gallery3d/ui/GLRootView.java b/new3d/src/com/android/gallery3d/ui/GLRootView.java
index a4c215b..659a4be 100644
--- a/new3d/src/com/android/gallery3d/ui/GLRootView.java
+++ b/new3d/src/com/android/gallery3d/ui/GLRootView.java
@@ -24,7 +24,8 @@
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.MotionEvent;
-import android.view.animation.Animation;
+
+import com.android.gallery3d.anim.CanvasAnimation;
 
 import java.util.ArrayList;
 import java.util.LinkedList;
@@ -64,7 +65,8 @@
     private final GalleryEGLConfigChooser mEglConfigChooser =
             new GalleryEGLConfigChooser();
 
-    private final ArrayList<Animation> mAnimations = new ArrayList<Animation>();
+    private final ArrayList<CanvasAnimation> mAnimations =
+            new ArrayList<CanvasAnimation>();
 
     private final LinkedList<OnGLIdleListener> mIdleListeners =
             new LinkedList<OnGLIdleListener>();
@@ -91,7 +93,7 @@
         return mEglConfigChooser;
     }
 
-    void registerLaunchedAnimation(Animation animation) {
+    void registerLaunchedAnimation(CanvasAnimation animation) {
         // Register the newly launched animation so that we can set the start
         // time more precisely. (Usually, it takes much longer for first
         // rendering, so we set the animation start time as the time we
@@ -204,13 +206,19 @@
         mRenderRequested = false;
 
         if ((mFlags & FLAG_NEED_LAYOUT) != 0) layoutContentPane();
-        mCanvas.clearClip();
         mCanvas.clearBuffer();
         mCanvas.setCurrentTimeMillis(SystemClock.uptimeMillis());
         if (mContentView != null) {
            mContentView.render(mCanvas);
         }
-        mAnimations.clear();
+
+        if (!mAnimations.isEmpty()) {
+            long now = SystemClock.uptimeMillis();
+            for (CanvasAnimation anim : mAnimations) {
+                anim.setStartTime(now);
+            }
+            mAnimations.clear();
+        }
 
         if (!mRenderRequested
                 && !mIdleRunner.mActive && !mIdleListeners.isEmpty()) {
diff --git a/new3d/src/com/android/gallery3d/ui/GLView.java b/new3d/src/com/android/gallery3d/ui/GLView.java
index a9e88d1..7b18d2a 100644
--- a/new3d/src/com/android/gallery3d/ui/GLView.java
+++ b/new3d/src/com/android/gallery3d/ui/GLView.java
@@ -16,12 +16,11 @@
 
 package com.android.gallery3d.ui;
 
-import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.SystemClock;
 import android.view.MotionEvent;
-import android.view.animation.Animation;
-import android.view.animation.Transformation;
+
+import com.android.gallery3d.anim.CanvasAnimation;
 
 import java.util.ArrayList;
 
@@ -45,7 +44,7 @@
     private GLView mMotionTarget;
 
     private OnTouchListener mOnTouchListener;
-    private Animation mAnimation;
+    private CanvasAnimation mAnimation;
 
     protected int mViewFlags = 0;
 
@@ -60,13 +59,11 @@
     protected int mScrollHeight = 0;
     protected int mScrollWidth = 0;
 
-    public void startAnimation(Animation animation) {
+    public void startAnimation(CanvasAnimation animation) {
         GLRootView root = getGLRootView();
         if (root == null) throw new IllegalStateException();
 
         mAnimation = animation;
-        animation.initialize(getWidth(),
-                getHeight(), mParent.getWidth(), mParent.getHeight());
         mAnimation.start();
         root.registerLaunchedAnimation(mAnimation);
         invalidate();
@@ -203,25 +200,21 @@
         int xoffset = component.mBounds.left - mScrollX;
         int yoffset = component.mBounds.top - mScrollY;
 
-        Transformation transform = canvas.getTransformation();
-        Matrix matrix = transform.getMatrix();
-        matrix.preTranslate(xoffset, yoffset);
+        canvas.translate(xoffset, yoffset, 0);
 
-        Animation anim = component.mAnimation;
+        CanvasAnimation anim = component.mAnimation;
         if (anim != null) {
-            long now = canvas.currentAnimationTimeMillis();
-            Transformation temp = canvas.obtainTransformation();
-            if (!anim.getTransformation(now, temp)) {
+            canvas.save(anim.getCanvasSaveFlags());
+            if (anim.calculate(canvas.currentAnimationTimeMillis())) {
+                invalidate();
+            } else {
                 component.mAnimation = null;
             }
-            invalidate();
-            canvas.pushTransform();
-            transform.compose(temp);
-            canvas.freeTransformation(temp);
+            anim.apply(canvas);
         }
         component.render(canvas);
-        if (anim != null) canvas.popTransform();
-        matrix.preTranslate(-xoffset, -yoffset);
+        if (anim != null) canvas.restore();
+        canvas.translate(-xoffset, -yoffset, 0);
     }
 
     protected boolean onTouch(MotionEvent event) {
diff --git a/new3d/src/com/android/gallery3d/ui/IconLabel.java b/new3d/src/com/android/gallery3d/ui/IconLabel.java
index c9c679e..0af1dc7 100644
--- a/new3d/src/com/android/gallery3d/ui/IconLabel.java
+++ b/new3d/src/com/android/gallery3d/ui/IconLabel.java
@@ -4,7 +4,6 @@
 import android.content.Context;
 import android.graphics.Color;
 import android.graphics.Rect;
-import android.view.animation.Transformation;
 
 public class IconLabel extends GLView {
 
@@ -91,9 +90,8 @@
 
         int xoffset = p.left;
 
-        Transformation trans = canvas.getTransformation();
-        float oldAlpha = trans.getAlpha();
-        trans.setAlpha(oldAlpha * (mEnabled ? ENABLED_ALPHA : DISABLED_ALPHA));
+        float oldAlpha = canvas.getAlpha();
+        canvas.multiplyAlpha(mEnabled ? ENABLED_ALPHA : DISABLED_ALPHA);
 
         BasicTexture icon = mIcon;
         if (icon != null) {
@@ -110,7 +108,7 @@
         //TODO: cut the text if it is too long
         title.draw(canvas, xoffset, yoffset);
 
-        trans.setAlpha(oldAlpha);
+        canvas.setAlpha(oldAlpha);
     }
 
     public void setEnabled(boolean enabled) {
diff --git a/new3d/src/com/android/gallery3d/ui/Pathbar.java b/new3d/src/com/android/gallery3d/ui/Pathbar.java
index ab06601..06cf4e0 100644
--- a/new3d/src/com/android/gallery3d/ui/Pathbar.java
+++ b/new3d/src/com/android/gallery3d/ui/Pathbar.java
@@ -1,10 +1,6 @@
 package com.android.gallery3d.ui;
 
 import static com.android.gallery3d.ui.Util.dpToPixel;
-
-import com.android.gallery3d.R;
-import com.android.gallery3d.anim.IntAnimation;
-
 import android.content.Context;
 import android.graphics.Color;
 import android.graphics.Rect;
@@ -12,7 +8,9 @@
 import android.os.Message;
 import android.util.Log;
 import android.view.MotionEvent;
-import android.view.animation.Transformation;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.anim.IntAnimation;
 
 import java.util.ArrayList;
 import java.util.NoSuchElementException;
@@ -245,17 +243,20 @@
         background.draw(
                 canvas, offsetX - p.left, 0, width + p.left + p.right, height);
 
-        int yoffset = (height - icon.getHeight()) / 2;
+        int offsetY = (height - icon.getHeight()) / 2;
         offsetX += sHorizontalPadding;
         if (icon != mProgressIcon) {
-            icon.draw(canvas, offsetX, yoffset);
+            icon.draw(canvas, offsetX, offsetY);
         } else {
-            Transformation t = canvas.pushTransform();
             int degrees = mProgressStep * 360 / PROGRESS_STEP_COUNT;
-            t.getMatrix().preRotate(degrees, offsetX
-                    + icon.getWidth() / 2, yoffset + icon.getHeight() / 2);
-            icon.draw(canvas, offsetX, yoffset);
-            canvas.popTransform();
+            int pivotX = offsetX + icon.getWidth() / 2;
+            int pivotY = offsetY + icon.getHeight() / 2;
+            canvas.save(GLCanvas.MATRIX_SAVE_FLAG);
+            canvas.translate(pivotX, pivotY, 0);
+            canvas.rotate(degrees, 0, 0, 1);
+            canvas.translate(-pivotX, -pivotY, 0);
+            icon.draw(canvas, offsetX, offsetY);
+            canvas.restore();
             if (!mProgressUpdated) {
                 mProgressUpdated = true;
                 mHandler.sendEmptyMessageDelayed(UPDATE_PROGRESS, 125);
@@ -263,8 +264,8 @@
         }
         offsetX += icon.getWidth();
         if (title != null) {
-            yoffset = (height - title.getHeight()) / 2;
-            title.draw(canvas, offsetX, yoffset);
+            offsetY = (height - title.getHeight()) / 2;
+            title.draw(canvas, offsetX, offsetY);
         }
         mOffsetX += width + p.right;
     }
diff --git a/new3d/src/com/android/gallery3d/ui/PopupWindow.java b/new3d/src/com/android/gallery3d/ui/PopupWindow.java
index 7f27b72..5867433 100644
--- a/new3d/src/com/android/gallery3d/ui/PopupWindow.java
+++ b/new3d/src/com/android/gallery3d/ui/PopupWindow.java
@@ -19,11 +19,12 @@
 import android.graphics.Rect;
 import android.util.Log;
 import android.view.View.MeasureSpec;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
 import android.view.animation.OvershootInterpolator;
-import android.view.animation.ScaleAnimation;
+
+import com.android.gallery3d.anim.AlphaAnimation;
+import com.android.gallery3d.anim.AnimationSet;
+import com.android.gallery3d.anim.CanvasAnimation;
+import com.android.gallery3d.anim.ScaleAnimation;
 
 import javax.microedition.khronos.opengles.GL11;
 
@@ -202,11 +203,10 @@
     public void popup() {
         setVisibility(GLView.VISIBLE);
 
-        AnimationSet set = new AnimationSet(false);
-        Animation scale = new ScaleAnimation(
+        AnimationSet set = new AnimationSet();
+        CanvasAnimation scale = new ScaleAnimation(
                 0.7f, 1f, 0.7f, 1f, mAnchorPosition, getHeight());
-        Animation alpha = new AlphaAnimation(0.5f, 1.0f);
-
+        CanvasAnimation alpha = new AlphaAnimation(0.5f, 1.0f);
         set.addAnimation(scale);
         set.addAnimation(alpha);
         scale.setDuration(150);
@@ -217,7 +217,7 @@
 
     public void popoff() {
         setVisibility(GLView.INVISIBLE);
-        Animation alpha = new AlphaAnimation(0.7f, 0.0f);
+        AlphaAnimation alpha = new AlphaAnimation(0.7f, 0.0f);
         alpha.setDuration(100);
         startAnimation(alpha);
     }