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.Handler_Delegate.IHandlerCallback;
29import android.os.Message;
30
31import java.util.PriorityQueue;
32import java.util.Queue;
33
34/**
35 * Abstract animation thread.
36 * <p/>
37 * This does not actually start an animation, instead it fakes a looper that will play whatever
38 * animation is sending messages to its own {@link Handler}.
39 * <p/>
40 * Classes should implement {@link #preAnimation()} and {@link #postAnimation()}.
41 * <p/>
42 * If {@link #preAnimation()} does not start an animation somehow then the thread doesn't do
43 * anything.
44 *
45 */
46public abstract class AnimationThread extends Thread {
47
48    private static class MessageBundle implements Comparable<MessageBundle> {
49        final Handler mTarget;
50        final Message mMessage;
51        final long mUptimeMillis;
52
53        MessageBundle(Handler target, Message message, long uptimeMillis) {
54            mTarget = target;
55            mMessage = message;
56            mUptimeMillis = uptimeMillis;
57        }
58
59        @Override
60        public int compareTo(MessageBundle bundle) {
61            if (mUptimeMillis < bundle.mUptimeMillis) {
62                return -1;
63            }
64            return 1;
65        }
66    }
67
68    private final RenderSessionImpl mSession;
69
70    private Queue<MessageBundle> mQueue = new PriorityQueue<MessageBundle>();
71    private final IAnimationListener mListener;
72
73    public AnimationThread(RenderSessionImpl scene, String threadName,
74            IAnimationListener listener) {
75        super(threadName);
76        mSession = scene;
77        mListener = listener;
78    }
79
80    public abstract Result preAnimation();
81    public abstract void postAnimation();
82
83    @Override
84    public void run() {
85        Bridge.prepareThread();
86        try {
87            /* FIXME: The ANIMATION_FRAME message no longer exists.  Instead, the
88             * animation timing loop is completely based on a Choreographer objects
89             * that schedules animation and drawing frames.  The animation handler is
90             * no longer even a handler; it is just a Runnable enqueued on the Choreographer.
91            Handler_Delegate.setCallback(new IHandlerCallback() {
92                @Override
93                public void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) {
94                    if (msg.what == ValueAnimator.ANIMATION_START ||
95                            msg.what == ValueAnimator.ANIMATION_FRAME) {
96                        mQueue.add(new MessageBundle(handler, msg, uptimeMillis));
97                    } else {
98                        // just ignore.
99                    }
100                }
101            });
102            */
103
104            // call out to the pre-animation work, which should start an animation or more.
105            Result result = preAnimation();
106            if (result.isSuccess() == false) {
107                mListener.done(result);
108            }
109
110            // loop the animation
111            RenderSession session = mSession.getSession();
112            do {
113                // check early.
114                if (mListener.isCanceled()) {
115                    break;
116                }
117
118                // get the next message.
119                MessageBundle bundle = mQueue.poll();
120                if (bundle == null) {
121                    break;
122                }
123
124                // sleep enough for this bundle to be on time
125                long currentTime = System.currentTimeMillis();
126                if (currentTime < bundle.mUptimeMillis) {
127                    try {
128                        sleep(bundle.mUptimeMillis - currentTime);
129                    } catch (InterruptedException e) {
130                        // FIXME log/do something/sleep again?
131                        e.printStackTrace();
132                    }
133                }
134
135                // check after sleeping.
136                if (mListener.isCanceled()) {
137                    break;
138                }
139
140                // ready to do the work, acquire the scene.
141                result = mSession.acquire(250);
142                if (result.isSuccess() == false) {
143                    mListener.done(result);
144                    return;
145                }
146
147                // process the bundle. If the animation is not finished, this will enqueue
148                // the next message, so mQueue will have another one.
149                try {
150                    // check after acquiring in case it took a while.
151                    if (mListener.isCanceled()) {
152                        break;
153                    }
154
155                    bundle.mTarget.handleMessage(bundle.mMessage);
156                    if (mSession.render(false /*freshRender*/).isSuccess()) {
157                        mListener.onNewFrame(session);
158                    }
159                } finally {
160                    mSession.release();
161                }
162            } while (mListener.isCanceled() == false && mQueue.size() > 0);
163
164            mListener.done(Status.SUCCESS.createResult());
165
166        } catch (Throwable throwable) {
167            // can't use Bridge.getLog() as the exception might be thrown outside
168            // of an acquire/release block.
169            mListener.done(Status.ERROR_UNKNOWN.createResult("Error playing animation", throwable));
170
171        } finally {
172            postAnimation();
173            Handler_Delegate.setCallback(null);
174            Bridge.cleanupThread();
175        }
176    }
177}
178