content: /*
   * Copyright (C) 2018 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 android.util.ArrayMap;
  import android.util.ArraySet;
  
  import java.io.PrintWriter;
  import java.util.ArrayList;
  
  /**
   * Keeps track of all {@link AppWindowToken} that are animating and makes sure all animations are
   * finished at the same time such that we don't run into issues with z-ordering: An activity A
   * that has a shorter animation that is above another activity B with a longer animation in the same
   * task, the animation layer would put the B on top of A, but from the hierarchy, A needs to be on
   * top of B. Thus, we defer reparenting A to the original hierarchy such that it stays on top of B
   * until B finishes animating.
   */
  class AnimatingAppWindowTokenRegistry {
  
      private ArraySet<AppWindowToken> mAnimatingTokens = new ArraySet<>();
      private ArrayMap<AppWindowToken, Runnable> mFinishedTokens = new ArrayMap<>();
  
      private ArrayList<Runnable> mTmpRunnableList = new ArrayList<>();
  
      private boolean mEndingDeferredFinish;
  
      /**
       * Notifies that an {@link AppWindowToken} has started animating.
       */
      void notifyStarting(AppWindowToken token) {
          mAnimatingTokens.add(token);
      }
  
      /**
       * Notifies that an {@link AppWindowToken} has finished animating.
       */
      void notifyFinished(AppWindowToken token) {
          mAnimatingTokens.remove(token);
          mFinishedTokens.remove(token);
  
          // If we were the last token, make sure the end all deferred finishes.
          if (mAnimatingTokens.isEmpty()) {
              endDeferringFinished();
          }
      }
  
      /**
       * Called when an {@link AppWindowToken} is about to finish animating.
       *
       * @param endDeferFinishCallback Callback to run when defer finish should be ended.
       * @return {@code true} if finishing the animation should be deferred, {@code false} otherwise.
       */
      boolean notifyAboutToFinish(AppWindowToken token, Runnable endDeferFinishCallback) {
          final boolean removed = mAnimatingTokens.remove(token);
          if (!removed) {
              return false;
          }
  
          if (mAnimatingTokens.isEmpty()) {
  
              // If no animations are animating anymore, finish all others.
              endDeferringFinished();
              return false;
          } else {
  
              // Otherwise let's put it into the pending list of to be finished animations.
              mFinishedTokens.put(token, endDeferFinishCallback);
              return true;
          }
      }
  
      private void endDeferringFinished() {
  
          // Don't start recursing. Running the finished listener invokes notifyFinished, which may
          // invoked us again.
          if (mEndingDeferredFinish) {
              return;
          }
          try {
              mEndingDeferredFinish = true;
  
              // Copy it into a separate temp list to avoid modifying the collection while iterating
              // as calling the callback may call back into notifyFinished.
              for (int i = mFinishedTokens.size() - 1; i >= 0; i--) {
                  mTmpRunnableList.add(mFinishedTokens.valueAt(i));
              }
              mFinishedTokens.clear();
              for (int i = mTmpRunnableList.size() - 1; i >= 0; i--) {
                  mTmpRunnableList.get(i).run();
              }
              mTmpRunnableList.clear();
          } finally {
              mEndingDeferredFinish = false;
          }
      }
  
      void dump(PrintWriter pw, String header, String prefix) {
          if (!mAnimatingTokens.isEmpty() || !mFinishedTokens.isEmpty()) {
              pw.print(prefix); pw.println(header);
              prefix = prefix + "  ";
              pw.print(prefix); pw.print("mAnimatingTokens="); pw.println(mAnimatingTokens);
              pw.print(prefix); pw.print("mFinishedTokens="); pw.println(mFinishedTokens);
          }
      }
  }
  