content: /*
   * Copyright (C) 2017 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License
   */
  
  package com.android.server.wm;
  
  import static android.util.TimeUtils.NANOS_PER_MS;
  import static android.view.Choreographer.CALLBACK_TRAVERSAL;
  import static android.view.Choreographer.getSfInstance;
  
  import android.animation.AnimationHandler;
  import android.animation.AnimationHandler.AnimationFrameCallbackProvider;
  import android.animation.Animator;
  import android.animation.AnimatorListenerAdapter;
  import android.animation.ValueAnimator;
  import android.annotation.Nullable;
  import android.hardware.power.V1_0.PowerHint;
  import android.os.PowerManagerInternal;
  import android.util.ArrayMap;
  import android.view.Choreographer;
  import android.view.SurfaceControl;
  import android.view.SurfaceControl.Transaction;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
  import com.android.server.AnimationThread;
  import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
  
  /**
   * Class to run animations without holding the window manager lock.
   */
  class SurfaceAnimationRunner {
  
      private final Object mLock = new Object();
  
      /**
       * Lock for cancelling animations. Must be acquired on it's own, or after acquiring
       * {@link #mLock}
       */
      private final Object mCancelLock = new Object();
  
      @VisibleForTesting
      Choreographer mChoreographer;
  
      private final Runnable mApplyTransactionRunnable = this::applyTransaction;
      private final AnimationHandler mAnimationHandler;
      private final Transaction mFrameTransaction;
      private final AnimatorFactory mAnimatorFactory;
      private final PowerManagerInternal mPowerManagerInternal;
      private boolean mApplyScheduled;
  
      @GuardedBy("mLock")
      @VisibleForTesting
      final ArrayMap<SurfaceControl, RunningAnimation> mPendingAnimations = new ArrayMap<>();
  
      @GuardedBy("mLock")
      @VisibleForTesting
      final ArrayMap<SurfaceControl, RunningAnimation> mRunningAnimations = new ArrayMap<>();
  
      @GuardedBy("mLock")
      private boolean mAnimationStartDeferred;
  
      SurfaceAnimationRunner(PowerManagerInternal powerManagerInternal) {
          this(null /* callbackProvider */, null /* animatorFactory */, new Transaction(),
                  powerManagerInternal);
      }
  
      @VisibleForTesting
      SurfaceAnimationRunner(@Nullable AnimationFrameCallbackProvider callbackProvider,
              AnimatorFactory animatorFactory, Transaction frameTransaction,
              PowerManagerInternal powerManagerInternal) {
          SurfaceAnimationThread.getHandler().runWithScissors(() -> mChoreographer = getSfInstance(),
                  0 /* timeout */);
          mFrameTransaction = frameTransaction;
          mAnimationHandler = new AnimationHandler();
          mAnimationHandler.setProvider(callbackProvider != null
                  ? callbackProvider
                  : new SfVsyncFrameCallbackProvider(mChoreographer));
          mAnimatorFactory = animatorFactory != null
                  ? animatorFactory
                  : SfValueAnimator::new;
          mPowerManagerInternal = powerManagerInternal;
      }
  
      /**
       * Defers starting of animations until {@link #continueStartingAnimations} is called. This
       * method is NOT nestable.
       *
       * @see #continueStartingAnimations
       */
      void deferStartingAnimations() {
          synchronized (mLock) {
              mAnimationStartDeferred = true;
          }
      }
  
      /**
       * Continues starting of animations.
       *
       * @see #deferStartingAnimations
       */
      void continueStartingAnimations() {
          synchronized (mLock) {
              mAnimationStartDeferred = false;
              if (!mPendingAnimations.isEmpty()) {
                  mChoreographer.postFrameCallback(this::startAnimations);
              }
          }
      }
  
      void startAnimation(AnimationSpec a, SurfaceControl animationLeash, Transaction t,
              Runnable finishCallback) {
          synchronized (mLock) {
              final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash,
                      finishCallback);
              mPendingAnimations.put(animationLeash, runningAnim);
              if (!mAnimationStartDeferred) {
                  mChoreographer.postFrameCallback(this::startAnimations);
              }
  
              // Some animations (e.g. move animations) require the initial transform to be applied
              // immediately.
              applyTransformation(runningAnim, t, 0 /* currentPlayTime */);
          }
      }
  
      void onAnimationCancelled(SurfaceControl leash) {
          synchronized (mLock) {
              if (mPendingAnimations.containsKey(leash)) {
                  mPendingAnimations.remove(leash);
                  return;
              }
              final RunningAnimation anim = mRunningAnimations.get(leash);
              if (anim != null) {
                  mRunningAnimations.remove(leash);
                  synchronized (mCancelLock) {
                      anim.mCancelled = true;
                  }
                  SurfaceAnimationThread.getHandler().post(() -> {
                      anim.mAnim.cancel();
                      applyTransaction();
                  });
              }
          }
      }
  
      @GuardedBy("mLock")
      private void startPendingAnimationsLocked() {
          for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
              startAnimationLocked(mPendingAnimations.valueAt(i));
          }
          mPendingAnimations.clear();
      }
  
      @GuardedBy("mLock")
      private void startAnimationLocked(RunningAnimation a) {
          final ValueAnimator anim = mAnimatorFactory.makeAnimator();
  
          // Animation length is already expected to be scaled.
          anim.overrideDurationScale(1.0f);
          anim.setDuration(a.mAnimSpec.getDuration());
          anim.addUpdateListener(animation -> {
              synchronized (mCancelLock) {
                  if (!a.mCancelled) {
                      final long duration = anim.getDuration();
                      long currentPlayTime = anim.getCurrentPlayTime();
                      if (currentPlayTime > duration) {
                          currentPlayTime = duration;
                      }
                      applyTransformation(a, mFrameTransaction, currentPlayTime);
                  }
              }
  
              // Transaction will be applied in the commit phase.
              scheduleApplyTransaction();
          });
  
          anim.addListener(new AnimatorListenerAdapter() {
              @Override
              public void onAnimationStart(Animator animation) {
                  synchronized (mCancelLock) {
                      if (!a.mCancelled) {
                          // TODO: change this back to use show instead of alpha when b/138459974 is
                          // fixed.
                          mFrameTransaction.setAlpha(a.mLeash, 1);
                      }
                  }
              }
  
              @Override
              public void onAnimationEnd(Animator animation) {
                  synchronized (mLock) {
                      mRunningAnimations.remove(a.mLeash);
                      synchronized (mCancelLock) {
                          if (!a.mCancelled) {
  
                              // Post on other thread that we can push final state without jank.
                              AnimationThread.getHandler().post(a.mFinishCallback);
                          }
                      }
                  }
              }
          });
          a.mAnim = anim;
          mRunningAnimations.put(a.mLeash, a);
  
          anim.start();
          if (a.mAnimSpec.canSkipFirstFrame()) {
              // If we can skip the first frame, we start one frame later.
              anim.setCurrentPlayTime(mChoreographer.getFrameIntervalNanos() / NANOS_PER_MS);
          }
  
          // Immediately start the animation by manually applying an animation frame. Otherwise, the
          // start time would only be set in the next frame, leading to a delay.
          anim.doAnimationFrame(mChoreographer.getFrameTime());
      }
  
      private void applyTransformation(RunningAnimation a, Transaction t, long currentPlayTime) {
          if (a.mAnimSpec.needsEarlyWakeup()) {
              t.setEarlyWakeup();
          }
          a.mAnimSpec.apply(t, a.mLeash, currentPlayTime);
      }
  
      private void startAnimations(long frameTimeNanos) {
          synchronized (mLock) {
              startPendingAnimationsLocked();
          }
          mPowerManagerInternal.powerHint(PowerHint.INTERACTION, 0);
      }
  
      private void scheduleApplyTransaction() {
          if (!mApplyScheduled) {
              mChoreographer.postCallback(CALLBACK_TRAVERSAL, mApplyTransactionRunnable,
                      null /* token */);
              mApplyScheduled = true;
          }
      }
  
      private void applyTransaction() {
          mFrameTransaction.setAnimationTransaction();
          mFrameTransaction.apply();
          mApplyScheduled = false;
      }
  
      private static final class RunningAnimation {
          final AnimationSpec mAnimSpec;
          final SurfaceControl mLeash;
          final Runnable mFinishCallback;
          ValueAnimator mAnim;
  
          @GuardedBy("mCancelLock")
          private boolean mCancelled;
  
          RunningAnimation(AnimationSpec animSpec, SurfaceControl leash, Runnable finishCallback) {
              mAnimSpec = animSpec;
              mLeash = leash;
              mFinishCallback = finishCallback;
          }
      }
  
      @VisibleForTesting
      interface AnimatorFactory {
          ValueAnimator makeAnimator();
      }
  
      /**
       * Value animator that uses sf-vsync signal to tick.
       */
      private class SfValueAnimator extends ValueAnimator {
  
          SfValueAnimator() {
              setFloatValues(0f, 1f);
          }
  
          @Override
          public AnimationHandler getAnimationHandler() {
              return mAnimationHandler;
          }
      }
  }
  