Implement rotation animations.

This introduces a small new feature for ScaleAnimation allowing
the scaling factor to be expressed as a percentage of the object
(which is the same as the existing float interpretation), a
percentage of the container, or a fixed dimension.  Maybe not
useful for anything else, but I needed it for this.

Also fix a bug in how transformation matrices were propagated
from the Animation to Surface Flinger, so that rotate and skew
animations will actually work. :p

Change-Id: I301f4caa2147aa35564b5e511cb9c0b368d2425d
diff --git a/services/java/com/android/server/ScreenRotationAnimation.java b/services/java/com/android/server/ScreenRotationAnimation.java
index 299567a..1cc6a2a 100644
--- a/services/java/com/android/server/ScreenRotationAnimation.java
+++ b/services/java/com/android/server/ScreenRotationAnimation.java
@@ -16,6 +16,7 @@
 
 package com.android.server;  // TODO: use com.android.server.wm, once things move there
 
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -28,38 +29,60 @@
 import android.view.Display;
 import android.view.Surface;
 import android.view.SurfaceSession;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Transformation;
 
 class ScreenRotationAnimation {
-    private static final String TAG = "ScreenRotationAnimation";
+    static final String TAG = "ScreenRotationAnimation";
+    static final boolean DEBUG = false;
 
+    final Context mContext;
+    final Display mDisplay;
     Surface mSurface;
     int mWidth, mHeight;
 
-    int mBaseRotation;
+    int mSnapshotRotation;
+    int mSnapshotDeltaRotation;
+    int mOriginalRotation;
+    int mOriginalWidth, mOriginalHeight;
     int mCurRotation;
-    int mDeltaRotation;
 
-    final Matrix mMatrix = new Matrix();
+    Animation mExitAnimation;
+    final Transformation mExitTransformation = new Transformation();
+    Animation mEnterAnimation;
+    final Transformation mEnterTransformation = new Transformation();
+    boolean mStarted;
+
+    final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+    final Matrix mSnapshotInitialMatrix = new Matrix();
+    final Matrix mSnapshotFinalMatrix = new Matrix();
     final float[] mTmpFloats = new float[9];
 
-    public ScreenRotationAnimation(Display display, SurfaceSession session) {
-        final DisplayMetrics dm = new DisplayMetrics();
-        display.getMetrics(dm);
+    public ScreenRotationAnimation(Context context, Display display, SurfaceSession session) {
+        mContext = context;
+        mDisplay = display;
+
+        display.getMetrics(mDisplayMetrics);
 
         Bitmap screenshot = Surface.screenshot(0, 0);
 
         if (screenshot != null) {
             // Screenshot does NOT include rotation!
-            mBaseRotation = 0;
+            mSnapshotRotation = 0;
             mWidth = screenshot.getWidth();
             mHeight = screenshot.getHeight();
         } else {
             // Just in case.
-            mBaseRotation = display.getRotation();
-            mWidth = dm.widthPixels;
-            mHeight = dm.heightPixels;
+            mSnapshotRotation = display.getRotation();
+            mWidth = mDisplayMetrics.widthPixels;
+            mHeight = mDisplayMetrics.heightPixels;
         }
 
+        mOriginalRotation = display.getRotation();
+        mOriginalWidth = mDisplayMetrics.widthPixels;
+        mOriginalHeight = mDisplayMetrics.heightPixels;
+
         Surface.openTransaction();
         if (mSurface != null) {
             mSurface.destroy();
@@ -102,43 +125,24 @@
         screenshot.recycle();
     }
 
-    // Must be called while in a transaction.
-    public void setRotation(int rotation) {
-        mCurRotation = rotation;
-        int delta = mCurRotation - mBaseRotation;
+    static int deltaRotation(int oldRotation, int newRotation) {
+        int delta = newRotation - oldRotation;
         if (delta < 0) delta += 4;
-        mDeltaRotation = delta;
+        return delta;
+    }
 
-        switch (delta) {
-            case Surface.ROTATION_0:
-                mMatrix.reset();
-                break;
-            case Surface.ROTATION_90:
-                mMatrix.setRotate(90, 0, 0);
-                mMatrix.postTranslate(0, mWidth);
-                break;
-            case Surface.ROTATION_180:
-                mMatrix.setRotate(180, 0, 0);
-                mMatrix.postTranslate(mWidth, mHeight);
-                break;
-            case Surface.ROTATION_270:
-                mMatrix.setRotate(270, 0, 0);
-                mMatrix.postTranslate(mHeight, 0);
-                break;
-        }
-
-        mMatrix.getValues(mTmpFloats);
+    void setSnapshotTransform(Matrix matrix, float alpha) {
+        matrix.getValues(mTmpFloats);
         mSurface.setPosition((int)mTmpFloats[Matrix.MTRANS_X],
                 (int)mTmpFloats[Matrix.MTRANS_Y]);
         mSurface.setMatrix(
-                mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_X],
-                mTmpFloats[Matrix.MSKEW_Y], mTmpFloats[Matrix.MSCALE_Y]);
-
-        if (false) {
+                mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
+                mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
+        mSurface.setAlpha(alpha);
+        if (DEBUG) {
             float[] srcPnts = new float[] { 0, 0, mWidth, mHeight };
-            float[] dstPnts = new float[8];
-            mMatrix.mapPoints(dstPnts, srcPnts);
-            Slog.i(TAG, "**** ROTATION: " + delta);
+            float[] dstPnts = new float[4];
+            matrix.mapPoints(dstPnts, srcPnts);
             Slog.i(TAG, "Original  : (" + srcPnts[0] + "," + srcPnts[1]
                     + ")-(" + srcPnts[2] + "," + srcPnts[3] + ")");
             Slog.i(TAG, "Transformed: (" + dstPnts[0] + "," + dstPnts[1]
@@ -146,7 +150,165 @@
         }
     }
 
-    public void dismiss() {
-        mSurface.destroy();
+    // Must be called while in a transaction.
+    public void setRotation(int rotation) {
+        mCurRotation = rotation;
+
+        // Compute the transformation matrix that must be applied
+        // to the snapshot to make it stay in the same original position
+        // with the current screen rotation.
+        int delta = deltaRotation(rotation, mSnapshotRotation);
+        switch (delta) {
+            case Surface.ROTATION_0:
+                mSnapshotInitialMatrix.reset();
+                break;
+            case Surface.ROTATION_90:
+                mSnapshotInitialMatrix.setRotate(90, 0, 0);
+                mSnapshotInitialMatrix.postTranslate(mHeight, 0);
+                break;
+            case Surface.ROTATION_180:
+                mSnapshotInitialMatrix.setRotate(180, 0, 0);
+                mSnapshotInitialMatrix.postTranslate(mWidth, mHeight);
+                break;
+            case Surface.ROTATION_270:
+                mSnapshotInitialMatrix.setRotate(270, 0, 0);
+                mSnapshotInitialMatrix.postTranslate(0, mWidth);
+                break;
+        }
+
+        if (DEBUG) Slog.v(TAG, "**** ROTATION: " + delta);
+        setSnapshotTransform(mSnapshotInitialMatrix, 1.0f);
+    }
+
+    /**
+     * Returns true if animating.
+     */
+    public boolean dismiss(long maxAnimationDuration, float animationScale) {
+        // Figure out how the screen has moved from the original rotation.
+        int delta = deltaRotation(mCurRotation, mOriginalRotation);
+        if (false && delta == 0) {
+            // Nothing changed, just remove the snapshot.
+            if (mSurface != null) {
+                mSurface.destroy();
+                mSurface = null;
+            }
+            return false;
+        }
+
+        switch (delta) {
+            case Surface.ROTATION_0:
+                mExitAnimation = AnimationUtils.loadAnimation(mContext,
+                        com.android.internal.R.anim.screen_rotate_0_exit);
+                mEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                        com.android.internal.R.anim.screen_rotate_0_enter);
+                break;
+            case Surface.ROTATION_90:
+                mExitAnimation = AnimationUtils.loadAnimation(mContext,
+                        com.android.internal.R.anim.screen_rotate_plus_90_exit);
+                mEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                        com.android.internal.R.anim.screen_rotate_plus_90_enter);
+                break;
+            case Surface.ROTATION_180:
+                mExitAnimation = AnimationUtils.loadAnimation(mContext,
+                        com.android.internal.R.anim.screen_rotate_180_exit);
+                mEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                        com.android.internal.R.anim.screen_rotate_180_enter);
+                break;
+            case Surface.ROTATION_270:
+                mExitAnimation = AnimationUtils.loadAnimation(mContext,
+                        com.android.internal.R.anim.screen_rotate_minus_90_exit);
+                mEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                        com.android.internal.R.anim.screen_rotate_minus_90_enter);
+                break;
+        }
+
+        mDisplay.getMetrics(mDisplayMetrics);
+
+        // Initialize the animations.  This is a hack, redefining what "parent"
+        // means to allow supplying the last and next size.  In this definition
+        // "%p" is the original (let's call it "previous") size, and "%" is the
+        // screen's current/new size.
+        mEnterAnimation.initialize(mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
+                mOriginalWidth, mOriginalHeight);
+        mExitAnimation.initialize(mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
+                mOriginalWidth, mOriginalHeight);
+        mStarted = false;
+
+        mExitAnimation.restrictDuration(maxAnimationDuration);
+        mExitAnimation.scaleCurrentDuration(animationScale);
+        mEnterAnimation.restrictDuration(maxAnimationDuration);
+        mEnterAnimation.scaleCurrentDuration(animationScale);
+
+        return true;
+    }
+
+    public void kill() {
+        if (mSurface != null) {
+            mSurface.destroy();
+            mSurface = null;
+        }
+        if (mExitAnimation != null) {
+            mExitAnimation.cancel();
+            mExitAnimation = null;
+        }
+        if (mEnterAnimation != null) {
+            mEnterAnimation.cancel();
+            mEnterAnimation = null;
+        }
+    }
+
+    public boolean isAnimating() {
+        return mEnterAnimation != null || mExitAnimation != null;
+    }
+
+    public boolean stepAnimation(long now) {
+        if (mEnterAnimation == null && mExitAnimation == null) {
+            return false;
+        }
+
+        if (!mStarted) {
+            mEnterAnimation.setStartTime(now);
+            mExitAnimation.setStartTime(now);
+            mStarted = true;
+        }
+
+        mExitTransformation.clear();
+        boolean moreExit = false;
+        if (mExitAnimation != null) {
+            moreExit = mExitAnimation.getTransformation(now, mExitTransformation);
+            if (DEBUG) Slog.v(TAG, "Stepped exit: " + mExitTransformation);
+            if (!moreExit) {
+                if (DEBUG) Slog.v(TAG, "Exit animation done!");
+                mExitAnimation.cancel();
+                mExitAnimation = null;
+                mExitTransformation.clear();
+                if (mSurface != null) {
+                    mSurface.destroy();
+                    mSurface = null;
+                }
+            }
+        }
+
+        mEnterTransformation.clear();
+        boolean moreEnter = false;
+        if (mEnterAnimation != null) {
+            moreEnter = mEnterAnimation.getTransformation(now, mEnterTransformation);
+            if (!moreEnter) {
+                mEnterAnimation.cancel();
+                mEnterAnimation = null;
+                mEnterTransformation.clear();
+            }
+        }
+
+        if (mSurface != null) {
+            mSnapshotFinalMatrix.setConcat(mExitTransformation.getMatrix(), mSnapshotInitialMatrix);
+            setSnapshotTransform(mSnapshotFinalMatrix, mExitTransformation.getAlpha());
+        }
+
+        return moreEnter || moreExit;
+    }
+
+    public Transformation getEnterTransformation() {
+        return mEnterTransformation;
     }
 }