1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.animation;
18
19import com.android.ide.common.rendering.api.IAnimationListener;
20import com.android.ide.common.rendering.api.RenderSession;
21import com.android.ide.common.rendering.api.Result;
22import com.android.ide.common.rendering.api.Result.Status;
23import com.android.layoutlib.bridge.Bridge;
24import com.android.layoutlib.bridge.impl.RenderSessionImpl;
25
26import android.os.Handler;
27import android.os.Handler_Delegate;
28import android.os.Message;
29
30import java.util.PriorityQueue;
31import java.util.Queue;
32
33/**
34 * Abstract animation thread.
35 * <p/>
36 * This does not actually start an animation, instead it fakes a looper that will play whatever
37 * animation is sending messages to its own {@link Handler}.
38 * <p/>
39 * Classes should implement {@link #preAnimation()} and {@link #postAnimation()}.
40 * <p/>
41 * If {@link #preAnimation()} does not start an animation somehow then the thread doesn't do
42 * anything.
43 *
44 */
45public abstract class AnimationThread extends Thread {
46
47    private static class MessageBundle implements Comparable<MessageBundle> {
48        final Handler mTarget;
49        final Message mMessage;
50        final long mUptimeMillis;
51
52        MessageBundle(Handler target, Message message, long uptimeMillis) {
53            mTarget = target;
54            mMessage = message;
55            mUptimeMillis = uptimeMillis;
56        }
57
58        @Override
59        public int compareTo(MessageBundle bundle) {
60            if (mUptimeMillis < bundle.mUptimeMillis) {
61                return -1;
62            }
63            return 1;
64        }
65    }
66
67    private final RenderSessionImpl mSession;
68
69    private Queue<MessageBundle> mQueue = new PriorityQueue<MessageBundle>();
70    private final IAnimationListener mListener;
71
72    public AnimationThread(RenderSessionImpl scene, String threadName,
73            IAnimationListener listener) {
74        super(threadName);
75        mSession = scene;
76        mListener = listener;
77    }
78
79    public abstract Result preAnimation();
80    public abstract void postAnimation();
81
82    @Override
83    public void run() {
84        Bridge.prepareThread();
85        try {
86            /* FIXME: The ANIMATION_FRAME message no longer exists.  Instead, the
87             * animation timing loop is completely based on a Choreographer objects
88             * that schedules animation and drawing frames.  The animation handler is
89             * no longer even a handler; it is just a Runnable enqueued on the Choreographer.
90            Handler_Delegate.setCallback(new IHandlerCallback() {
91                @Override
92                public void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) {
93                    if (msg.what == ValueAnimator.ANIMATION_START ||
94                            msg.what == ValueAnimator.ANIMATION_FRAME) {
95                        mQueue.add(new MessageBundle(handler, msg, uptimeMillis));
96                    } else {
97                        // just ignore.
98                    }
99                }
100            });
101            */
102
103            // call out to the pre-animation work, which should start an animation or more.
104            Result result = preAnimation();
105            if (result.isSuccess() == false) {
106                mListener.done(result);
107            }
108
109            // loop the animation
110            RenderSession session = mSession.getSession();
111            do {
112                // check early.
113                if (mListener.isCanceled()) {
114                    break;
115                }
116
117                // get the next message.
118                MessageBundle bundle = mQueue.poll();
119                if (bundle == null) {
120                    break;
121                }
122
123                // sleep enough for this bundle to be on time
124                long currentTime = System.currentTimeMillis();
125                if (currentTime < bundle.mUptimeMillis) {
126                    try {
127                        sleep(bundle.mUptimeMillis - currentTime);
128                    } catch (InterruptedException e) {
129                        // FIXME log/do something/sleep again?
130                        e.printStackTrace();
131                    }
132                }
133
134                // check after sleeping.
135                if (mListener.isCanceled()) {
136                    break;
137                }
138
139                // ready to do the work, acquire the scene.
140                result = mSession.acquire(250);
141                if (result.isSuccess() == false) {
142                    mListener.done(result);
143                    return;
144                }
145
146                // process the bundle. If the animation is not finished, this will enqueue
147                // the next message, so mQueue will have another one.
148                try {
149                    // check after acquiring in case it took a while.
150                    if (mListener.isCanceled()) {
151                        break;
152                    }
153
154                    bundle.mTarget.handleMessage(bundle.mMessage);
155                    if (mSession.render(false /*freshRender*/).isSuccess()) {
156                        mListener.onNewFrame(session);
157                    }
158                } finally {
159                    mSession.release();
160                }
161            } while (mListener.isCanceled() == false && mQueue.size() > 0);
162
163            mListener.done(Status.SUCCESS.createResult());
164
165        } catch (Throwable throwable) {
166            // can't use Bridge.getLog() as the exception might be thrown outside
167            // of an acquire/release block.
168            mListener.done(Status.ERROR_UNKNOWN.createResult("Error playing animation", throwable));
169
170        } finally {
171            postAnimation();
172            Handler_Delegate.setCallback(null);
173            Bridge.cleanupThread();
174        }
175    }
176}
177