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