blob: ce2aec790119e00b1b79f2c65768a2ff89732c3c [file] [log] [blame]
Adam Lesinski282e1812014-01-23 18:17:42 -08001/*
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;
Adam Lesinski282e1812014-01-23 18:17:42 -080028import 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}