1 /*
2  * Copyright (C) 2021 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 
17 package com.android.wm.shell.transition;
18 
19 import static android.util.RotationUtils.deltaRotation;
20 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
21 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
22 import static android.view.WindowManagerPolicyConstants.SCREEN_FREEZE_LAYER_BASE;
23 
24 import static com.android.wm.shell.transition.DefaultTransitionHandler.buildSurfaceAnimation;
25 import static com.android.wm.shell.transition.Transitions.TAG;
26 
27 import android.animation.Animator;
28 import android.animation.AnimatorListenerAdapter;
29 import android.animation.ArgbEvaluator;
30 import android.animation.ValueAnimator;
31 import android.annotation.NonNull;
32 import android.content.Context;
33 import android.graphics.Color;
34 import android.graphics.Matrix;
35 import android.graphics.Rect;
36 import android.hardware.HardwareBuffer;
37 import android.util.Slog;
38 import android.view.Surface;
39 import android.view.SurfaceControl;
40 import android.view.SurfaceControl.Transaction;
41 import android.view.SurfaceSession;
42 import android.view.animation.Animation;
43 import android.view.animation.AnimationUtils;
44 import android.window.ScreenCapture;
45 import android.window.TransitionInfo;
46 
47 import com.android.internal.R;
48 import com.android.internal.policy.TransitionAnimation;
49 import com.android.wm.shell.common.ShellExecutor;
50 import com.android.wm.shell.common.TransactionPool;
51 
52 import java.util.ArrayList;
53 
54 /**
55  * This class handles the rotation animation when the device is rotated.
56  *
57  * <p>
58  * The screen rotation animation is composed of 3 different part:
59  * <ul>
60  * <li> The screenshot: <p>
61  *     A screenshot of the whole screen prior the change of orientation is taken to hide the
62  *     element resizing below. The screenshot is then animated to rotate and cross-fade to
63  *     the new orientation with the content in the new orientation.
64  *
65  * <li> The windows on the display: <p>y
66  *      Once the device is rotated, the screen and its content are in the new orientation. The
67  *      animation first rotate the new content into the old orientation to then be able to
68  *      animate to the new orientation
69  *
70  * <li> The Background color frame: <p>
71  *      To have the animation seem more seamless, we add a color transitioning background behind the
72  *      exiting and entering layouts. We compute the brightness of the start and end
73  *      layouts and transition from the two brightness values as grayscale underneath the animation
74  * </ul>
75  */
76 class ScreenRotationAnimation {
77     static final int MAX_ANIMATION_DURATION = 10 * 1000;
78 
79     private final Context mContext;
80     private final TransactionPool mTransactionPool;
81     private final float[] mTmpFloats = new float[9];
82     /** The leash of the changing window container. */
83     private final SurfaceControl mSurfaceControl;
84 
85     private final int mAnimHint;
86     private final int mStartWidth;
87     private final int mStartHeight;
88     private final int mEndWidth;
89     private final int mEndHeight;
90     private final int mStartRotation;
91     private final int mEndRotation;
92 
93     /** This layer contains the actual screenshot that is to be faded out. */
94     private SurfaceControl mScreenshotLayer;
95     /**
96      * Only used for screen rotation and not custom animations. Layered behind all other layers
97      * to avoid showing any "empty" spots
98      */
99     private SurfaceControl mBackColorSurface;
100     /** The leash using to animate screenshot layer. */
101     private final SurfaceControl mAnimLeash;
102 
103     // The current active animation to move from the old to the new rotated
104     // state.  Which animation is run here will depend on the old and new
105     // rotations.
106     private Animation mRotateExitAnimation;
107     private Animation mRotateEnterAnimation;
108     private Animation mRotateAlphaAnimation;
109 
110     /** Intensity of light/whiteness of the layout before rotation occurs. */
111     private float mStartLuma;
112     /** Intensity of light/whiteness of the layout after rotation occurs. */
113     private float mEndLuma;
114 
ScreenRotationAnimation(Context context, SurfaceSession session, TransactionPool pool, Transaction t, TransitionInfo.Change change, SurfaceControl rootLeash, int animHint)115     ScreenRotationAnimation(Context context, SurfaceSession session, TransactionPool pool,
116             Transaction t, TransitionInfo.Change change, SurfaceControl rootLeash, int animHint) {
117         mContext = context;
118         mTransactionPool = pool;
119         mAnimHint = animHint;
120 
121         mSurfaceControl = change.getLeash();
122         mStartWidth = change.getStartAbsBounds().width();
123         mStartHeight = change.getStartAbsBounds().height();
124         mEndWidth = change.getEndAbsBounds().width();
125         mEndHeight = change.getEndAbsBounds().height();
126         mStartRotation = change.getStartRotation();
127         mEndRotation = change.getEndRotation();
128 
129         mAnimLeash = new SurfaceControl.Builder(session)
130                 .setParent(rootLeash)
131                 .setEffectLayer()
132                 .setCallsite("ShellRotationAnimation")
133                 .setName("Animation leash of screenshot rotation")
134                 .build();
135 
136         try {
137             if (change.getSnapshot() != null) {
138                 mScreenshotLayer = change.getSnapshot();
139                 t.reparent(mScreenshotLayer, mAnimLeash);
140                 mStartLuma = change.getSnapshotLuma();
141             } else {
142                 ScreenCapture.LayerCaptureArgs args =
143                         new ScreenCapture.LayerCaptureArgs.Builder(mSurfaceControl)
144                                 .setCaptureSecureLayers(true)
145                                 .setAllowProtected(true)
146                                 .setSourceCrop(new Rect(0, 0, mStartWidth, mStartHeight))
147                                 .setHintForSeamlessTransition(true)
148                                 .build();
149                 ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
150                         ScreenCapture.captureLayers(args);
151                 if (screenshotBuffer == null) {
152                     Slog.w(TAG, "Unable to take screenshot of display");
153                     return;
154                 }
155 
156                 mScreenshotLayer = new SurfaceControl.Builder(session)
157                         .setParent(mAnimLeash)
158                         .setBLASTLayer()
159                         .setSecure(screenshotBuffer.containsSecureLayers())
160                         .setOpaque(true)
161                         .setCallsite("ShellRotationAnimation")
162                         .setName("RotationLayer")
163                         .build();
164 
165                 TransitionAnimation.configureScreenshotLayer(t, mScreenshotLayer, screenshotBuffer);
166                 final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
167                 t.show(mScreenshotLayer);
168                 if (!isCustomRotate()) {
169                     mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer,
170                             screenshotBuffer.getColorSpace(), mSurfaceControl);
171                 }
172                 hardwareBuffer.close();
173             }
174 
175             t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
176             t.show(mAnimLeash);
177             // Crop the real content in case it contains a larger child layer, e.g. wallpaper.
178             t.setCrop(mSurfaceControl, new Rect(0, 0, mEndWidth, mEndHeight));
179 
180             if (!isCustomRotate()) {
181                 mBackColorSurface = new SurfaceControl.Builder(session)
182                         .setParent(rootLeash)
183                         .setColorLayer()
184                         .setOpaque(true)
185                         .setCallsite("ShellRotationAnimation")
186                         .setName("BackColorSurface")
187                         .build();
188 
189                 t.setLayer(mBackColorSurface, -1);
190                 t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
191                 t.show(mBackColorSurface);
192             }
193 
194         } catch (Surface.OutOfResourcesException e) {
195             Slog.w(TAG, "Unable to allocate freeze surface", e);
196         }
197 
198         setScreenshotTransform(t);
199         t.apply();
200     }
201 
isCustomRotate()202     private boolean isCustomRotate() {
203         return mAnimHint == ROTATION_ANIMATION_CROSSFADE || mAnimHint == ROTATION_ANIMATION_JUMPCUT;
204     }
205 
setScreenshotTransform(SurfaceControl.Transaction t)206     private void setScreenshotTransform(SurfaceControl.Transaction t) {
207         if (mScreenshotLayer == null) {
208             return;
209         }
210         final Matrix matrix = new Matrix();
211         final int delta = deltaRotation(mEndRotation, mStartRotation);
212         if (delta != 0) {
213             // Compute the transformation matrix that must be applied to the snapshot to make it
214             // stay in the same original position with the current screen rotation.
215             switch (delta) {
216                 case Surface.ROTATION_90:
217                     matrix.setRotate(90, 0, 0);
218                     matrix.postTranslate(mStartHeight, 0);
219                     break;
220                 case Surface.ROTATION_180:
221                     matrix.setRotate(180, 0, 0);
222                     matrix.postTranslate(mStartWidth, mStartHeight);
223                     break;
224                 case Surface.ROTATION_270:
225                     matrix.setRotate(270, 0, 0);
226                     matrix.postTranslate(0, mStartWidth);
227                     break;
228             }
229         } else if ((mEndWidth > mStartWidth) == (mEndHeight > mStartHeight)
230                 && (mEndWidth != mStartWidth || mEndHeight != mStartHeight)) {
231             // Display resizes without rotation change.
232             final float scale = Math.max((float) mEndWidth / mStartWidth,
233                     (float) mEndHeight / mStartHeight);
234             matrix.setScale(scale, scale);
235         }
236         matrix.getValues(mTmpFloats);
237         float x = mTmpFloats[Matrix.MTRANS_X];
238         float y = mTmpFloats[Matrix.MTRANS_Y];
239         t.setPosition(mScreenshotLayer, x, y);
240         t.setMatrix(mScreenshotLayer,
241                 mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
242                 mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
243     }
244 
245     /**
246      * Returns true if any animations were added to `animations`.
247      */
buildAnimation(@onNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, float animationScale, @NonNull ShellExecutor mainExecutor)248     boolean buildAnimation(@NonNull ArrayList<Animator> animations,
249             @NonNull Runnable finishCallback, float animationScale,
250             @NonNull ShellExecutor mainExecutor) {
251         if (mScreenshotLayer == null) {
252             // Can't do animation.
253             return false;
254         }
255 
256         // TODO : Found a way to get right end luma and re-enable color frame animation.
257         // End luma value is very not stable so it will cause more flicker is we run background
258         // color frame animation.
259         //mEndLuma = getLumaOfSurfaceControl(mEndBounds, mSurfaceControl);
260 
261         final boolean customRotate = isCustomRotate();
262         if (customRotate) {
263             mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
264                     mAnimHint == ROTATION_ANIMATION_JUMPCUT ? R.anim.rotation_animation_jump_exit
265                             : R.anim.rotation_animation_xfade_exit);
266             mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
267                     R.anim.rotation_animation_enter);
268             mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext,
269                     R.anim.screen_rotate_alpha);
270         } else {
271             // Figure out how the screen has moved from the original rotation.
272             int delta = deltaRotation(mEndRotation, mStartRotation);
273             switch (delta) { /* Counter-Clockwise Rotations */
274                 case Surface.ROTATION_0:
275                     mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
276                             R.anim.screen_rotate_0_exit);
277                     mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
278                             R.anim.rotation_animation_enter);
279                     break;
280                 case Surface.ROTATION_90:
281                     mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
282                             R.anim.screen_rotate_plus_90_exit);
283                     mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
284                             R.anim.screen_rotate_plus_90_enter);
285                     break;
286                 case Surface.ROTATION_180:
287                     mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
288                             R.anim.screen_rotate_180_exit);
289                     mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
290                             R.anim.screen_rotate_180_enter);
291                     break;
292                 case Surface.ROTATION_270:
293                     mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
294                             R.anim.screen_rotate_minus_90_exit);
295                     mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
296                             R.anim.screen_rotate_minus_90_enter);
297                     break;
298             }
299         }
300 
301         mRotateExitAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
302         mRotateExitAnimation.restrictDuration(MAX_ANIMATION_DURATION);
303         mRotateExitAnimation.scaleCurrentDuration(animationScale);
304         mRotateEnterAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
305         mRotateEnterAnimation.restrictDuration(MAX_ANIMATION_DURATION);
306         mRotateEnterAnimation.scaleCurrentDuration(animationScale);
307 
308         if (customRotate) {
309             mRotateAlphaAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
310             mRotateAlphaAnimation.restrictDuration(MAX_ANIMATION_DURATION);
311             mRotateAlphaAnimation.scaleCurrentDuration(animationScale);
312 
313             buildScreenshotAlphaAnimation(animations, finishCallback, mainExecutor);
314             startDisplayRotation(animations, finishCallback, mainExecutor);
315         } else {
316             startDisplayRotation(animations, finishCallback, mainExecutor);
317             startScreenshotRotationAnimation(animations, finishCallback, mainExecutor);
318             //startColorAnimation(mTransaction, animationScale);
319         }
320 
321         return true;
322     }
323 
startDisplayRotation(@onNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor)324     private void startDisplayRotation(@NonNull ArrayList<Animator> animations,
325             @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
326         buildSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback,
327                 mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
328                 null /* clipRect */);
329     }
330 
startScreenshotRotationAnimation(@onNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor)331     private void startScreenshotRotationAnimation(@NonNull ArrayList<Animator> animations,
332             @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
333         buildSurfaceAnimation(animations, mRotateExitAnimation, mAnimLeash, finishCallback,
334                 mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
335                 null /* clipRect */);
336     }
337 
buildScreenshotAlphaAnimation(@onNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor)338     private void buildScreenshotAlphaAnimation(@NonNull ArrayList<Animator> animations,
339             @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
340         buildSurfaceAnimation(animations, mRotateAlphaAnimation, mAnimLeash, finishCallback,
341                 mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
342                 null /* clipRect */);
343     }
344 
startColorAnimation(float animationScale, @NonNull ShellExecutor animExecutor)345     private void startColorAnimation(float animationScale, @NonNull ShellExecutor animExecutor) {
346         int colorTransitionMs = mContext.getResources().getInteger(
347                 R.integer.config_screen_rotation_color_transition);
348         final float[] rgbTmpFloat = new float[3];
349         final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma);
350         final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma);
351         final long duration = colorTransitionMs * (long) animationScale;
352         final Transaction t = mTransactionPool.acquire();
353 
354         final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
355         // Animation length is already expected to be scaled.
356         va.overrideDurationScale(1.0f);
357         va.setDuration(duration);
358         va.addUpdateListener(animation -> {
359             final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
360             final float fraction = currentPlayTime / va.getDuration();
361             applyColor(startColor, endColor, rgbTmpFloat, fraction, mBackColorSurface, t);
362         });
363         va.addListener(new AnimatorListenerAdapter() {
364             @Override
365             public void onAnimationCancel(Animator animation) {
366                 applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface,
367                         t);
368                 mTransactionPool.release(t);
369             }
370 
371             @Override
372             public void onAnimationEnd(Animator animation) {
373                 applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface,
374                         t);
375                 mTransactionPool.release(t);
376             }
377         });
378         animExecutor.execute(va::start);
379     }
380 
kill()381     public void kill() {
382         final Transaction t = mTransactionPool.acquire();
383         if (mAnimLeash.isValid()) {
384             t.remove(mAnimLeash);
385         }
386 
387         if (mScreenshotLayer != null && mScreenshotLayer.isValid()) {
388             t.remove(mScreenshotLayer);
389         }
390         if (mBackColorSurface != null && mBackColorSurface.isValid()) {
391             t.remove(mBackColorSurface);
392         }
393         t.apply();
394         mTransactionPool.release(t);
395     }
396 
applyColor(int startColor, int endColor, float[] rgbFloat, float fraction, SurfaceControl surface, SurfaceControl.Transaction t)397     private static void applyColor(int startColor, int endColor, float[] rgbFloat,
398             float fraction, SurfaceControl surface, SurfaceControl.Transaction t) {
399         final int color = (Integer) ArgbEvaluator.getInstance().evaluate(fraction, startColor,
400                 endColor);
401         Color middleColor = Color.valueOf(color);
402         rgbFloat[0] = middleColor.red();
403         rgbFloat[1] = middleColor.green();
404         rgbFloat[2] = middleColor.blue();
405         if (surface.isValid()) {
406             t.setColor(surface, rgbFloat);
407         }
408         t.apply();
409     }
410 }
411