/*
* 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.Handler_Delegate.IHandlerCallback;
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();
}
}
}