1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.wm;
18
19import android.content.Context;
20import android.graphics.Bitmap;
21import android.graphics.Canvas;
22import android.graphics.Matrix;
23import android.graphics.Paint;
24import android.graphics.PixelFormat;
25import android.graphics.PorterDuff;
26import android.graphics.PorterDuffXfermode;
27import android.graphics.Rect;
28import android.util.Slog;
29import android.view.Surface;
30import android.view.SurfaceSession;
31import android.view.animation.Animation;
32import android.view.animation.AnimationUtils;
33import android.view.animation.Transformation;
34
35class ScreenRotationAnimation {
36    static final String TAG = "ScreenRotationAnimation";
37    static final boolean DEBUG = false;
38
39    static final int FREEZE_LAYER = WindowManagerService.TYPE_LAYER_MULTIPLIER * 200;
40
41    final Context mContext;
42    Surface mSurface;
43    BlackFrame mBlackFrame;
44    int mWidth, mHeight;
45
46    int mSnapshotRotation;
47    int mSnapshotDeltaRotation;
48    int mOriginalRotation;
49    int mOriginalWidth, mOriginalHeight;
50    int mCurRotation;
51
52    Animation mExitAnimation;
53    final Transformation mExitTransformation = new Transformation();
54    Animation mEnterAnimation;
55    final Transformation mEnterTransformation = new Transformation();
56    boolean mStarted;
57
58    final Matrix mSnapshotInitialMatrix = new Matrix();
59    final Matrix mSnapshotFinalMatrix = new Matrix();
60    final Matrix mTmpMatrix = new Matrix();
61    final float[] mTmpFloats = new float[9];
62
63    public ScreenRotationAnimation(Context context, SurfaceSession session,
64            boolean inTransaction, int originalWidth, int originalHeight, int originalRotation) {
65        mContext = context;
66
67        // Screenshot does NOT include rotation!
68        mSnapshotRotation = 0;
69        if (originalRotation == Surface.ROTATION_90
70                || originalRotation == Surface.ROTATION_270) {
71            mWidth = originalHeight;
72            mHeight = originalWidth;
73        } else {
74            mWidth = originalWidth;
75            mHeight = originalHeight;
76        }
77
78        mOriginalRotation = originalRotation;
79        mOriginalWidth = originalWidth;
80        mOriginalHeight = originalHeight;
81
82        if (!inTransaction) {
83            if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(WindowManagerService.TAG,
84                    ">>> OPEN TRANSACTION ScreenRotationAnimation");
85            Surface.openTransaction();
86        }
87
88        try {
89            try {
90                mSurface = new Surface(session, 0, "FreezeSurface",
91                        -1, mWidth, mHeight, PixelFormat.OPAQUE, Surface.FX_SURFACE_SCREENSHOT | Surface.HIDDEN);
92                if (mSurface == null || !mSurface.isValid()) {
93                    // Screenshot failed, punt.
94                    mSurface = null;
95                    return;
96                }
97                mSurface.setLayer(FREEZE_LAYER + 1);
98                mSurface.show();
99            } catch (Surface.OutOfResourcesException e) {
100                Slog.w(TAG, "Unable to allocate freeze surface", e);
101            }
102
103            if (WindowManagerService.SHOW_TRANSACTIONS ||
104                    WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG,
105                            "  FREEZE " + mSurface + ": CREATE");
106
107            setRotation(originalRotation);
108        } finally {
109            if (!inTransaction) {
110                Surface.closeTransaction();
111                if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(WindowManagerService.TAG,
112                        "<<< CLOSE TRANSACTION ScreenRotationAnimation");
113            }
114        }
115    }
116
117    boolean hasScreenshot() {
118        return mSurface != null;
119    }
120
121    static int deltaRotation(int oldRotation, int newRotation) {
122        int delta = newRotation - oldRotation;
123        if (delta < 0) delta += 4;
124        return delta;
125    }
126
127    void setSnapshotTransform(Matrix matrix, float alpha) {
128        if (mSurface != null) {
129            matrix.getValues(mTmpFloats);
130            mSurface.setPosition(mTmpFloats[Matrix.MTRANS_X],
131                    mTmpFloats[Matrix.MTRANS_Y]);
132            mSurface.setMatrix(
133                    mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
134                    mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
135            mSurface.setAlpha(alpha);
136            if (DEBUG) {
137                float[] srcPnts = new float[] { 0, 0, mWidth, mHeight };
138                float[] dstPnts = new float[4];
139                matrix.mapPoints(dstPnts, srcPnts);
140                Slog.i(TAG, "Original  : (" + srcPnts[0] + "," + srcPnts[1]
141                        + ")-(" + srcPnts[2] + "," + srcPnts[3] + ")");
142                Slog.i(TAG, "Transformed: (" + dstPnts[0] + "," + dstPnts[1]
143                        + ")-(" + dstPnts[2] + "," + dstPnts[3] + ")");
144            }
145        }
146    }
147
148    public static void createRotationMatrix(int rotation, int width, int height,
149            Matrix outMatrix) {
150        switch (rotation) {
151            case Surface.ROTATION_0:
152                outMatrix.reset();
153                break;
154            case Surface.ROTATION_90:
155                outMatrix.setRotate(90, 0, 0);
156                outMatrix.postTranslate(height, 0);
157                break;
158            case Surface.ROTATION_180:
159                outMatrix.setRotate(180, 0, 0);
160                outMatrix.postTranslate(width, height);
161                break;
162            case Surface.ROTATION_270:
163                outMatrix.setRotate(270, 0, 0);
164                outMatrix.postTranslate(0, width);
165                break;
166        }
167    }
168
169    // Must be called while in a transaction.
170    public void setRotation(int rotation) {
171        mCurRotation = rotation;
172
173        // Compute the transformation matrix that must be applied
174        // to the snapshot to make it stay in the same original position
175        // with the current screen rotation.
176        int delta = deltaRotation(rotation, mSnapshotRotation);
177        createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix);
178
179        if (DEBUG) Slog.v(TAG, "**** ROTATION: " + delta);
180        setSnapshotTransform(mSnapshotInitialMatrix, 1.0f);
181    }
182
183    /**
184     * Returns true if animating.
185     */
186    public boolean dismiss(SurfaceSession session, long maxAnimationDuration,
187            float animationScale, int finalWidth, int finalHeight) {
188        if (mSurface == null) {
189            // Can't do animation.
190            return false;
191        }
192
193        // Figure out how the screen has moved from the original rotation.
194        int delta = deltaRotation(mCurRotation, mOriginalRotation);
195
196        switch (delta) {
197            case Surface.ROTATION_0:
198                mExitAnimation = AnimationUtils.loadAnimation(mContext,
199                        com.android.internal.R.anim.screen_rotate_0_exit);
200                mEnterAnimation = AnimationUtils.loadAnimation(mContext,
201                        com.android.internal.R.anim.screen_rotate_0_enter);
202                break;
203            case Surface.ROTATION_90:
204                mExitAnimation = AnimationUtils.loadAnimation(mContext,
205                        com.android.internal.R.anim.screen_rotate_plus_90_exit);
206                mEnterAnimation = AnimationUtils.loadAnimation(mContext,
207                        com.android.internal.R.anim.screen_rotate_plus_90_enter);
208                break;
209            case Surface.ROTATION_180:
210                mExitAnimation = AnimationUtils.loadAnimation(mContext,
211                        com.android.internal.R.anim.screen_rotate_180_exit);
212                mEnterAnimation = AnimationUtils.loadAnimation(mContext,
213                        com.android.internal.R.anim.screen_rotate_180_enter);
214                break;
215            case Surface.ROTATION_270:
216                mExitAnimation = AnimationUtils.loadAnimation(mContext,
217                        com.android.internal.R.anim.screen_rotate_minus_90_exit);
218                mEnterAnimation = AnimationUtils.loadAnimation(mContext,
219                        com.android.internal.R.anim.screen_rotate_minus_90_enter);
220                break;
221        }
222
223        // Initialize the animations.  This is a hack, redefining what "parent"
224        // means to allow supplying the last and next size.  In this definition
225        // "%p" is the original (let's call it "previous") size, and "%" is the
226        // screen's current/new size.
227        mEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
228        mExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
229        mStarted = false;
230
231        mExitAnimation.restrictDuration(maxAnimationDuration);
232        mExitAnimation.scaleCurrentDuration(animationScale);
233        mEnterAnimation.restrictDuration(maxAnimationDuration);
234        mEnterAnimation.scaleCurrentDuration(animationScale);
235
236        if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(WindowManagerService.TAG,
237                ">>> OPEN TRANSACTION ScreenRotationAnimation.dismiss");
238        Surface.openTransaction();
239
240        try {
241            Rect outer = new Rect(-finalWidth, -finalHeight, finalWidth * 2, finalHeight * 2);
242            Rect inner = new Rect(0, 0, finalWidth, finalHeight);
243            mBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER);
244        } catch (Surface.OutOfResourcesException e) {
245            Slog.w(TAG, "Unable to allocate black surface", e);
246        } finally {
247            Surface.closeTransaction();
248            if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(WindowManagerService.TAG,
249                    "<<< CLOSE TRANSACTION ScreenRotationAnimation.dismiss");
250        }
251
252        return true;
253    }
254
255    public void kill() {
256        if (mSurface != null) {
257            if (WindowManagerService.SHOW_TRANSACTIONS ||
258                    WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG,
259                            "  FREEZE " + mSurface + ": DESTROY");
260            mSurface.destroy();
261            mSurface = null;
262        }
263        if (mBlackFrame != null) {
264            mBlackFrame.kill();
265        }
266        if (mExitAnimation != null) {
267            mExitAnimation.cancel();
268            mExitAnimation = null;
269        }
270        if (mEnterAnimation != null) {
271            mEnterAnimation.cancel();
272            mEnterAnimation = null;
273        }
274    }
275
276    public boolean isAnimating() {
277        return mEnterAnimation != null || mExitAnimation != null;
278    }
279
280    public boolean stepAnimation(long now) {
281        if (mEnterAnimation == null && mExitAnimation == null) {
282            return false;
283        }
284
285        if (!mStarted) {
286            if (mEnterAnimation != null) {
287                mEnterAnimation.setStartTime(now);
288            }
289            if (mExitAnimation != null) {
290                mExitAnimation.setStartTime(now);
291            }
292            mStarted = true;
293        }
294
295        mExitTransformation.clear();
296        boolean moreExit = false;
297        if (mExitAnimation != null) {
298            moreExit = mExitAnimation.getTransformation(now, mExitTransformation);
299            if (DEBUG) Slog.v(TAG, "Stepped exit: " + mExitTransformation);
300            if (!moreExit) {
301                if (DEBUG) Slog.v(TAG, "Exit animation done!");
302                mExitAnimation.cancel();
303                mExitAnimation = null;
304                mExitTransformation.clear();
305                if (mSurface != null) {
306                    mSurface.hide();
307                }
308            }
309        }
310
311        mEnterTransformation.clear();
312        boolean moreEnter = false;
313        if (mEnterAnimation != null) {
314            moreEnter = mEnterAnimation.getTransformation(now, mEnterTransformation);
315            if (!moreEnter) {
316                mEnterAnimation.cancel();
317                mEnterAnimation = null;
318                mEnterTransformation.clear();
319                if (mBlackFrame != null) {
320                    mBlackFrame.hide();
321                }
322            } else {
323                if (mBlackFrame != null) {
324                    mBlackFrame.setMatrix(mEnterTransformation.getMatrix());
325                }
326            }
327        }
328
329        mSnapshotFinalMatrix.setConcat(mExitTransformation.getMatrix(), mSnapshotInitialMatrix);
330        setSnapshotTransform(mSnapshotFinalMatrix, mExitTransformation.getAlpha());
331
332        return moreEnter || moreExit;
333    }
334
335    public Transformation getEnterTransformation() {
336        return mEnterTransformation;
337    }
338}
339