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