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;
}
}