content: /*
   * Copyright (C) 2010 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 android.animation;
  
  import com.android.ide.common.rendering.api.IAnimationListener;
  import com.android.ide.common.rendering.api.RenderSession;
  import com.android.ide.common.rendering.api.Result;
  import com.android.ide.common.rendering.api.Result.Status;
  import com.android.layoutlib.bridge.Bridge;
  import com.android.layoutlib.bridge.impl.RenderSessionImpl;
  
  import android.os.Handler;
  import android.os.Handler_Delegate;
  import android.os.Message;
  
  import java.util.PriorityQueue;
  import java.util.Queue;
  
  /**
   * Abstract animation thread.
   * <p/>
   * This does not actually start an animation, instead it fakes a looper that will play whatever
   * animation is sending messages to its own {@link Handler}.
   * <p/>
   * Classes should implement {@link #preAnimation()} and {@link #postAnimation()}.
   * <p/>
   * If {@link #preAnimation()} does not start an animation somehow then the thread doesn't do
   * anything.
   *
   */
  public abstract class AnimationThread extends Thread {
  
      private static class MessageBundle implements Comparable<MessageBundle> {
          final Handler mTarget;
          final Message mMessage;
          final long mUptimeMillis;
  
          MessageBundle(Handler target, Message message, long uptimeMillis) {
              mTarget = target;
              mMessage = message;
              mUptimeMillis = uptimeMillis;
          }
  
          @Override
          public int compareTo(MessageBundle bundle) {
              if (mUptimeMillis < bundle.mUptimeMillis) {
                  return -1;
              }
              return 1;
          }
      }
  
      private final RenderSessionImpl mSession;
  
      private Queue<MessageBundle> mQueue = new PriorityQueue<MessageBundle>();
      private final IAnimationListener mListener;
  
      public AnimationThread(RenderSessionImpl scene, String threadName,
              IAnimationListener listener) {
          super(threadName);
          mSession = scene;
          mListener = listener;
      }
  
      public abstract Result preAnimation();
      public abstract void postAnimation();
  
      @Override
      public void run() {
          Bridge.prepareThread();
          try {
              /* FIXME: The ANIMATION_FRAME message no longer exists.  Instead, the
               * animation timing loop is completely based on a Choreographer objects
               * that schedules animation and drawing frames.  The animation handler is
               * no longer even a handler; it is just a Runnable enqueued on the Choreographer.
              Handler_Delegate.setCallback(new IHandlerCallback() {
                  @Override
                  public void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) {
                      if (msg.what == ValueAnimator.ANIMATION_START ||
                              msg.what == ValueAnimator.ANIMATION_FRAME) {
                          mQueue.add(new MessageBundle(handler, msg, uptimeMillis));
                      } else {
                          // just ignore.
                      }
                  }
              });
              */
  
              // call out to the pre-animation work, which should start an animation or more.
              Result result = preAnimation();
              if (result.isSuccess() == false) {
                  mListener.done(result);
              }
  
              // loop the animation
              RenderSession session = mSession.getSession();
              do {
                  // check early.
                  if (mListener.isCanceled()) {
                      break;
                  }
  
                  // get the next message.
                  MessageBundle bundle = mQueue.poll();
                  if (bundle == null) {
                      break;
                  }
  
                  // sleep enough for this bundle to be on time
                  long currentTime = System.currentTimeMillis();
                  if (currentTime < bundle.mUptimeMillis) {
                      try {
                          sleep(bundle.mUptimeMillis - currentTime);
                      } catch (InterruptedException e) {
                          // FIXME log/do something/sleep again?
                          e.printStackTrace();
                      }
                  }
  
                  // check after sleeping.
                  if (mListener.isCanceled()) {
                      break;
                  }
  
                  // ready to do the work, acquire the scene.
                  result = mSession.acquire(250);
                  if (result.isSuccess() == false) {
                      mListener.done(result);
                      return;
                  }
  
                  // process the bundle. If the animation is not finished, this will enqueue
                  // the next message, so mQueue will have another one.
                  try {
                      // check after acquiring in case it took a while.
                      if (mListener.isCanceled()) {
                          break;
                      }
  
                      bundle.mTarget.handleMessage(bundle.mMessage);
                      if (mSession.render(false /*freshRender*/).isSuccess()) {
                          mListener.onNewFrame(session);
                      }
                  } finally {
                      mSession.release();
                  }
              } while (mListener.isCanceled() == false && mQueue.size() > 0);
  
              mListener.done(Status.SUCCESS.createResult());
  
          } catch (Throwable throwable) {
              // can't use Bridge.getLog() as the exception might be thrown outside
              // of an acquire/release block.
              mListener.done(Status.ERROR_UNKNOWN.createResult("Error playing animation", throwable));
  
          } finally {
              postAnimation();
              Handler_Delegate.setCallback(null);
              Bridge.cleanupThread();
          }
      }
  }
  