1 /* 2 * Copyright (C) 2017 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.server.wm; 18 19 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; 20 import static android.util.TimeUtils.NANOS_PER_MS; 21 import static android.view.Choreographer.CALLBACK_TRAVERSAL; 22 import static android.view.Choreographer.getSfInstance; 23 24 import android.animation.AnimationHandler; 25 import android.animation.AnimationHandler.AnimationFrameCallbackProvider; 26 import android.animation.Animator; 27 import android.animation.AnimatorListenerAdapter; 28 import android.animation.ValueAnimator; 29 import android.annotation.Nullable; 30 import android.graphics.BitmapShader; 31 import android.graphics.Canvas; 32 import android.graphics.Insets; 33 import android.graphics.Paint; 34 import android.graphics.PixelFormat; 35 import android.graphics.Rect; 36 import android.hardware.power.Boost; 37 import android.os.Handler; 38 import android.os.PowerManagerInternal; 39 import android.os.Trace; 40 import android.util.ArrayMap; 41 import android.util.Log; 42 import android.view.Choreographer; 43 import android.view.Surface; 44 import android.view.SurfaceControl; 45 import android.view.SurfaceControl.Transaction; 46 import android.view.animation.Animation; 47 import android.view.animation.Transformation; 48 import android.window.ScreenCapture; 49 50 import com.android.internal.annotations.GuardedBy; 51 import com.android.internal.annotations.VisibleForTesting; 52 import com.android.internal.graphics.SfVsyncFrameCallbackProvider; 53 import com.android.server.AnimationThread; 54 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec; 55 56 import java.util.ArrayList; 57 import java.util.concurrent.ExecutorService; 58 import java.util.concurrent.Executors; 59 import java.util.function.Supplier; 60 61 /** 62 * Class to run animations without holding the window manager lock. 63 */ 64 class SurfaceAnimationRunner { 65 66 private static final String TAG = SurfaceAnimationRunner.class.getSimpleName(); 67 68 private final Object mLock = new Object(); 69 70 /** 71 * Lock for cancelling animations. Must be acquired on it's own, or after acquiring 72 * {@link #mLock} 73 */ 74 private final Object mCancelLock = new Object(); 75 76 /** 77 * Lock for synchronizing {@link #mEdgeExtensions} to prevent race conditions when managing 78 * created edge extension surfaces. 79 */ 80 private final Object mEdgeExtensionLock = new Object(); 81 82 @VisibleForTesting 83 Choreographer mChoreographer; 84 85 private final Handler mAnimationThreadHandler = AnimationThread.getHandler(); 86 private final Handler mSurfaceAnimationHandler = SurfaceAnimationThread.getHandler(); 87 private final Runnable mApplyTransactionRunnable = this::applyTransaction; 88 private final AnimationHandler mAnimationHandler; 89 private final Transaction mFrameTransaction; 90 private final AnimatorFactory mAnimatorFactory; 91 private final PowerManagerInternal mPowerManagerInternal; 92 private boolean mApplyScheduled; 93 94 // Executor to perform the edge extension. 95 // With two threads because in practice we will want to extend two surfaces in one animation, 96 // in which case we want to be able to parallelize those two extensions to cut down latency in 97 // starting the animation. 98 private final ExecutorService mEdgeExtensionExecutor = Executors.newFixedThreadPool(2); 99 100 @GuardedBy("mLock") 101 @VisibleForTesting 102 final ArrayMap<SurfaceControl, RunningAnimation> mPendingAnimations = new ArrayMap<>(); 103 104 @GuardedBy("mLock") 105 @VisibleForTesting 106 final ArrayMap<SurfaceControl, RunningAnimation> mPreProcessingAnimations = new ArrayMap<>(); 107 108 @GuardedBy("mLock") 109 @VisibleForTesting 110 final ArrayMap<SurfaceControl, RunningAnimation> mRunningAnimations = new ArrayMap<>(); 111 112 @GuardedBy("mLock") 113 private boolean mAnimationStartDeferred; 114 115 // Mapping animation leashes to a list of edge extension surfaces associated with them 116 @GuardedBy("mEdgeExtensionLock") 117 private final ArrayMap<SurfaceControl, ArrayList<SurfaceControl>> mEdgeExtensions = 118 new ArrayMap<>(); 119 120 /** 121 * There should only ever be one instance of this class. Usual spot for it is with 122 * {@link WindowManagerService} 123 */ SurfaceAnimationRunner(Supplier<Transaction> transactionFactory, PowerManagerInternal powerManagerInternal)124 SurfaceAnimationRunner(Supplier<Transaction> transactionFactory, 125 PowerManagerInternal powerManagerInternal) { 126 this(null /* callbackProvider */, null /* animatorFactory */, 127 transactionFactory.get(), powerManagerInternal); 128 } 129 130 @VisibleForTesting SurfaceAnimationRunner(@ullable AnimationFrameCallbackProvider callbackProvider, AnimatorFactory animatorFactory, Transaction frameTransaction, PowerManagerInternal powerManagerInternal)131 SurfaceAnimationRunner(@Nullable AnimationFrameCallbackProvider callbackProvider, 132 AnimatorFactory animatorFactory, Transaction frameTransaction, 133 PowerManagerInternal powerManagerInternal) { 134 mSurfaceAnimationHandler.runWithScissors(() -> mChoreographer = getSfInstance(), 135 0 /* timeout */); 136 mFrameTransaction = frameTransaction; 137 mAnimationHandler = new AnimationHandler(); 138 mAnimationHandler.setProvider(callbackProvider != null 139 ? callbackProvider 140 : new SfVsyncFrameCallbackProvider(mChoreographer)); 141 mAnimatorFactory = animatorFactory != null 142 ? animatorFactory 143 : SfValueAnimator::new; 144 mPowerManagerInternal = powerManagerInternal; 145 } 146 147 /** 148 * Defers starting of animations until {@link #continueStartingAnimations} is called. This 149 * method is NOT nestable. 150 * 151 * @see #continueStartingAnimations 152 */ deferStartingAnimations()153 void deferStartingAnimations() { 154 synchronized (mLock) { 155 mAnimationStartDeferred = true; 156 } 157 } 158 159 /** 160 * Continues starting of animations. 161 * 162 * @see #deferStartingAnimations 163 */ continueStartingAnimations()164 void continueStartingAnimations() { 165 synchronized (mLock) { 166 mAnimationStartDeferred = false; 167 if (!mPendingAnimations.isEmpty() && mPreProcessingAnimations.isEmpty()) { 168 mChoreographer.postFrameCallback(this::startAnimations); 169 } 170 } 171 } 172 startAnimation(AnimationSpec a, SurfaceControl animationLeash, Transaction t, Runnable finishCallback)173 void startAnimation(AnimationSpec a, SurfaceControl animationLeash, Transaction t, 174 Runnable finishCallback) { 175 synchronized (mLock) { 176 final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash, 177 finishCallback); 178 boolean requiresEdgeExtension = requiresEdgeExtension(a); 179 180 if (requiresEdgeExtension) { 181 final ArrayList<SurfaceControl> extensionSurfaces = new ArrayList<>(); 182 synchronized (mEdgeExtensionLock) { 183 mEdgeExtensions.put(animationLeash, extensionSurfaces); 184 } 185 186 mPreProcessingAnimations.put(animationLeash, runningAnim); 187 188 // We must wait for t to be committed since otherwise the leash doesn't have the 189 // windows we want to screenshot and extend as children. 190 t.addTransactionCommittedListener(mEdgeExtensionExecutor, () -> { 191 if (!animationLeash.isValid()) { 192 Log.e(TAG, "Animation leash is not valid"); 193 synchronized (mEdgeExtensionLock) { 194 mEdgeExtensions.remove(animationLeash); 195 } 196 synchronized (mLock) { 197 mPreProcessingAnimations.remove(animationLeash); 198 } 199 return; 200 } 201 final WindowAnimationSpec animationSpec = a.asWindowAnimationSpec(); 202 203 final Transaction edgeExtensionCreationTransaction = new Transaction(); 204 edgeExtendWindow(animationLeash, 205 animationSpec.getRootTaskBounds(), animationSpec.getAnimation(), 206 edgeExtensionCreationTransaction); 207 208 synchronized (mLock) { 209 // only run if animation is not yet canceled by this point 210 if (mPreProcessingAnimations.get(animationLeash) == runningAnim) { 211 // In the case the animation is cancelled, edge extensions are removed 212 // onAnimationLeashLost which is called before onAnimationCancelled. 213 // So we need to check if the edge extensions have already been removed 214 // or not, and if so we don't want to apply the transaction. 215 synchronized (mEdgeExtensionLock) { 216 if (!mEdgeExtensions.isEmpty()) { 217 edgeExtensionCreationTransaction.apply(); 218 } 219 } 220 221 mPreProcessingAnimations.remove(animationLeash); 222 mPendingAnimations.put(animationLeash, runningAnim); 223 if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) { 224 mChoreographer.postFrameCallback(this::startAnimations); 225 } 226 } 227 } 228 }); 229 } 230 231 if (!requiresEdgeExtension) { 232 mPendingAnimations.put(animationLeash, runningAnim); 233 if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) { 234 mChoreographer.postFrameCallback(this::startAnimations); 235 } 236 } 237 238 // Some animations (e.g. move animations) require the initial transform to be 239 // applied immediately. 240 applyTransformation(runningAnim, t, 0 /* currentPlayTime */); 241 } 242 } 243 requiresEdgeExtension(AnimationSpec a)244 private boolean requiresEdgeExtension(AnimationSpec a) { 245 return a.asWindowAnimationSpec() != null && a.asWindowAnimationSpec().hasExtension(); 246 } 247 onAnimationCancelled(SurfaceControl leash)248 void onAnimationCancelled(SurfaceControl leash) { 249 synchronized (mLock) { 250 if (mPendingAnimations.containsKey(leash)) { 251 mPendingAnimations.remove(leash); 252 return; 253 } 254 if (mPreProcessingAnimations.containsKey(leash)) { 255 mPreProcessingAnimations.remove(leash); 256 return; 257 } 258 final RunningAnimation anim = mRunningAnimations.get(leash); 259 if (anim != null) { 260 mRunningAnimations.remove(leash); 261 synchronized (mCancelLock) { 262 anim.mCancelled = true; 263 } 264 mSurfaceAnimationHandler.post(() -> { 265 anim.mAnim.cancel(); 266 applyTransaction(); 267 }); 268 } 269 } 270 } 271 272 @GuardedBy("mLock") startPendingAnimationsLocked()273 private void startPendingAnimationsLocked() { 274 for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { 275 startAnimationLocked(mPendingAnimations.valueAt(i)); 276 } 277 mPendingAnimations.clear(); 278 } 279 280 @GuardedBy("mLock") startAnimationLocked(RunningAnimation a)281 private void startAnimationLocked(RunningAnimation a) { 282 final ValueAnimator anim = mAnimatorFactory.makeAnimator(); 283 284 // Animation length is already expected to be scaled. 285 anim.overrideDurationScale(1.0f); 286 anim.setDuration(a.mAnimSpec.getDuration()); 287 anim.addUpdateListener(animation -> { 288 synchronized (mCancelLock) { 289 if (!a.mCancelled) { 290 final long duration = anim.getDuration(); 291 long currentPlayTime = anim.getCurrentPlayTime(); 292 if (currentPlayTime > duration) { 293 currentPlayTime = duration; 294 } 295 applyTransformation(a, mFrameTransaction, currentPlayTime); 296 } 297 } 298 299 // Transaction will be applied in the commit phase. 300 scheduleApplyTransaction(); 301 }); 302 303 anim.addListener(new AnimatorListenerAdapter() { 304 @Override 305 public void onAnimationStart(Animator animation) { 306 synchronized (mCancelLock) { 307 if (!a.mCancelled) { 308 // TODO: change this back to use show instead of alpha when b/138459974 is 309 // fixed. 310 mFrameTransaction.setAlpha(a.mLeash, 1); 311 } 312 } 313 } 314 315 @Override 316 public void onAnimationEnd(Animator animation) { 317 synchronized (mLock) { 318 mRunningAnimations.remove(a.mLeash); 319 synchronized (mCancelLock) { 320 if (!a.mCancelled) { 321 322 // Post on other thread that we can push final state without jank. 323 mAnimationThreadHandler.post(a.mFinishCallback); 324 } 325 } 326 } 327 } 328 }); 329 a.mAnim = anim; 330 mRunningAnimations.put(a.mLeash, a); 331 332 anim.start(); 333 if (a.mAnimSpec.canSkipFirstFrame()) { 334 // If we can skip the first frame, we start one frame later. 335 anim.setCurrentPlayTime(mChoreographer.getFrameIntervalNanos() / NANOS_PER_MS); 336 } 337 338 // Immediately start the animation by manually applying an animation frame. Otherwise, the 339 // start time would only be set in the next frame, leading to a delay. 340 anim.doAnimationFrame(mChoreographer.getFrameTime()); 341 } 342 applyTransformation(RunningAnimation a, Transaction t, long currentPlayTime)343 private void applyTransformation(RunningAnimation a, Transaction t, long currentPlayTime) { 344 a.mAnimSpec.apply(t, a.mLeash, currentPlayTime); 345 } 346 startAnimations(long frameTimeNanos)347 private void startAnimations(long frameTimeNanos) { 348 synchronized (mLock) { 349 if (!mPreProcessingAnimations.isEmpty()) { 350 // We only want to start running animations once all mPreProcessingAnimations have 351 // been processed to ensure preprocessed animations start in sync. 352 // NOTE: This means we might delay running animations that require preprocessing if 353 // new animations that also require preprocessing are requested before the previous 354 // ones have finished (see b/227449117). 355 return; 356 } 357 startPendingAnimationsLocked(); 358 } 359 mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0); 360 } 361 scheduleApplyTransaction()362 private void scheduleApplyTransaction() { 363 if (!mApplyScheduled) { 364 mChoreographer.postCallback(CALLBACK_TRAVERSAL, mApplyTransactionRunnable, 365 null /* token */); 366 mApplyScheduled = true; 367 } 368 } 369 applyTransaction()370 private void applyTransaction() { 371 mFrameTransaction.setAnimationTransaction(); 372 mFrameTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId()); 373 mFrameTransaction.apply(); 374 mApplyScheduled = false; 375 } 376 edgeExtendWindow(SurfaceControl leash, Rect bounds, Animation a, Transaction transaction)377 private void edgeExtendWindow(SurfaceControl leash, Rect bounds, Animation a, 378 Transaction transaction) { 379 final Transformation transformationAtStart = new Transformation(); 380 a.getTransformationAt(0, transformationAtStart); 381 final Transformation transformationAtEnd = new Transformation(); 382 a.getTransformationAt(1, transformationAtEnd); 383 384 // We want to create an extension surface that is the maximal size and the animation will 385 // take care of cropping any part that overflows. 386 final Insets maxExtensionInsets = Insets.min( 387 transformationAtStart.getInsets(), transformationAtEnd.getInsets()); 388 389 final int targetSurfaceHeight = bounds.height(); 390 final int targetSurfaceWidth = bounds.width(); 391 392 if (maxExtensionInsets.left < 0) { 393 final Rect edgeBounds = new Rect(bounds.left, bounds.top, bounds.left + 1, 394 bounds.bottom); 395 final Rect extensionRect = new Rect(0, 0, 396 -maxExtensionInsets.left, targetSurfaceHeight); 397 final int xPos = bounds.left + maxExtensionInsets.left; 398 final int yPos = bounds.top; 399 createExtensionSurface(leash, edgeBounds, 400 extensionRect, xPos, yPos, "Left Edge Extension", transaction); 401 } 402 403 if (maxExtensionInsets.top < 0) { 404 final Rect edgeBounds = new Rect(bounds.left, bounds.top, targetSurfaceWidth, 405 bounds.top + 1); 406 final Rect extensionRect = new Rect(0, 0, 407 targetSurfaceWidth, -maxExtensionInsets.top); 408 final int xPos = bounds.left; 409 final int yPos = bounds.top + maxExtensionInsets.top; 410 createExtensionSurface(leash, edgeBounds, 411 extensionRect, xPos, yPos, "Top Edge Extension", transaction); 412 } 413 414 if (maxExtensionInsets.right < 0) { 415 final Rect edgeBounds = new Rect(bounds.right - 1, bounds.top, bounds.right, 416 bounds.bottom); 417 final Rect extensionRect = new Rect(0, 0, 418 -maxExtensionInsets.right, targetSurfaceHeight); 419 final int xPos = bounds.right; 420 final int yPos = bounds.top; 421 createExtensionSurface(leash, edgeBounds, 422 extensionRect, xPos, yPos, "Right Edge Extension", transaction); 423 } 424 425 if (maxExtensionInsets.bottom < 0) { 426 final Rect edgeBounds = new Rect(bounds.left, bounds.bottom - 1, 427 bounds.right, bounds.bottom); 428 final Rect extensionRect = new Rect(0, 0, 429 targetSurfaceWidth, -maxExtensionInsets.bottom); 430 final int xPos = bounds.left; 431 final int yPos = bounds.bottom; 432 createExtensionSurface(leash, edgeBounds, 433 extensionRect, xPos, yPos, "Bottom Edge Extension", transaction); 434 } 435 } 436 createExtensionSurface(SurfaceControl leash, Rect edgeBounds, Rect extensionRect, int xPos, int yPos, String layerName, Transaction startTransaction)437 private void createExtensionSurface(SurfaceControl leash, Rect edgeBounds, 438 Rect extensionRect, int xPos, int yPos, String layerName, 439 Transaction startTransaction) { 440 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createExtensionSurface"); 441 doCreateExtensionSurface(leash, edgeBounds, extensionRect, xPos, yPos, layerName, 442 startTransaction); 443 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 444 } 445 doCreateExtensionSurface(SurfaceControl leash, Rect edgeBounds, Rect extensionRect, int xPos, int yPos, String layerName, Transaction startTransaction)446 private void doCreateExtensionSurface(SurfaceControl leash, Rect edgeBounds, 447 Rect extensionRect, int xPos, int yPos, String layerName, 448 Transaction startTransaction) { 449 ScreenCapture.LayerCaptureArgs captureArgs = 450 new ScreenCapture.LayerCaptureArgs.Builder(leash /* surfaceToExtend */) 451 .setSourceCrop(edgeBounds) 452 .setFrameScale(1) 453 .setPixelFormat(PixelFormat.RGBA_8888) 454 .setChildrenOnly(true) 455 .setAllowProtected(true) 456 .setCaptureSecureLayers(true) 457 .build(); 458 final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer = 459 ScreenCapture.captureLayers(captureArgs); 460 461 if (edgeBuffer == null) { 462 // The leash we are trying to screenshot may have been removed by this point, which is 463 // likely the reason for ending up with a null edgeBuffer, in which case we just want to 464 // return and do nothing. 465 Log.e(TAG, "Failed to create edge extension - edge buffer is null"); 466 return; 467 } 468 469 final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder() 470 .setName(layerName) 471 .setHidden(true) 472 .setCallsite("DefaultTransitionHandler#startAnimation") 473 .setOpaque(true) 474 .setBufferSize(extensionRect.width(), extensionRect.height()) 475 .build(); 476 477 BitmapShader shader = new BitmapShader(edgeBuffer.asBitmap(), 478 android.graphics.Shader.TileMode.CLAMP, 479 android.graphics.Shader.TileMode.CLAMP); 480 final Paint paint = new Paint(); 481 paint.setShader(shader); 482 483 final Surface surface = new Surface(edgeExtensionLayer); 484 Canvas c = surface.lockHardwareCanvas(); 485 c.drawRect(extensionRect, paint); 486 surface.unlockCanvasAndPost(c); 487 surface.release(); 488 489 synchronized (mEdgeExtensionLock) { 490 if (!mEdgeExtensions.containsKey(leash)) { 491 // The animation leash has already been removed, so we don't want to attach the 492 // edgeExtension layer and should immediately remove it instead. 493 startTransaction.remove(edgeExtensionLayer); 494 return; 495 } 496 497 startTransaction.reparent(edgeExtensionLayer, leash); 498 startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE); 499 startTransaction.setPosition(edgeExtensionLayer, xPos, yPos); 500 startTransaction.setVisibility(edgeExtensionLayer, true); 501 502 mEdgeExtensions.get(leash).add(edgeExtensionLayer); 503 } 504 } 505 getScaleXForExtensionSurface(Rect edgeBounds, Rect extensionRect)506 private float getScaleXForExtensionSurface(Rect edgeBounds, Rect extensionRect) { 507 if (edgeBounds.width() == extensionRect.width()) { 508 // Top or bottom edge extension, no need to scale the X axis of the extension surface. 509 return 1; 510 } 511 if (edgeBounds.width() == 1) { 512 // Left or right edge extension, scale the surface to be the extensionRect's width. 513 return extensionRect.width(); 514 } 515 516 throw new RuntimeException("Unexpected edgeBounds and extensionRect widths"); 517 } 518 getScaleYForExtensionSurface(Rect edgeBounds, Rect extensionRect)519 private float getScaleYForExtensionSurface(Rect edgeBounds, Rect extensionRect) { 520 if (edgeBounds.height() == extensionRect.height()) { 521 // Left or right edge extension, no need to scale the Y axis of the extension surface. 522 return 1; 523 } 524 if (edgeBounds.height() == 1) { 525 // Top or bottom edge extension, scale the surface to be the extensionRect's height. 526 return extensionRect.height(); 527 } 528 529 throw new RuntimeException("Unexpected edgeBounds and extensionRect heights"); 530 } 531 532 private static final class RunningAnimation { 533 final AnimationSpec mAnimSpec; 534 final SurfaceControl mLeash; 535 final Runnable mFinishCallback; 536 ValueAnimator mAnim; 537 538 @GuardedBy("mCancelLock") 539 private boolean mCancelled; 540 RunningAnimation(AnimationSpec animSpec, SurfaceControl leash, Runnable finishCallback)541 RunningAnimation(AnimationSpec animSpec, SurfaceControl leash, Runnable finishCallback) { 542 mAnimSpec = animSpec; 543 mLeash = leash; 544 mFinishCallback = finishCallback; 545 } 546 } 547 onAnimationLeashLost(SurfaceControl animationLeash, Transaction t)548 protected void onAnimationLeashLost(SurfaceControl animationLeash, 549 Transaction t) { 550 synchronized (mEdgeExtensionLock) { 551 if (!mEdgeExtensions.containsKey(animationLeash)) { 552 return; 553 } 554 555 final ArrayList<SurfaceControl> edgeExtensions = mEdgeExtensions.get(animationLeash); 556 for (int i = 0; i < edgeExtensions.size(); i++) { 557 final SurfaceControl extension = edgeExtensions.get(i); 558 t.remove(extension); 559 } 560 mEdgeExtensions.remove(animationLeash); 561 } 562 } 563 564 @VisibleForTesting 565 interface AnimatorFactory { makeAnimator()566 ValueAnimator makeAnimator(); 567 } 568 569 /** 570 * Value animator that uses sf-vsync signal to tick. 571 */ 572 private class SfValueAnimator extends ValueAnimator { 573 SfValueAnimator()574 SfValueAnimator() { 575 setFloatValues(0f, 1f); 576 } 577 578 @Override getAnimationHandler()579 public AnimationHandler getAnimationHandler() { 580 return mAnimationHandler; 581 } 582 } 583 }