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 
17 package android.animation;
18 
19 import com.android.ide.common.rendering.api.IAnimationListener;
20 import com.android.ide.common.rendering.api.RenderSession;
21 import com.android.ide.common.rendering.api.Result;
22 import com.android.ide.common.rendering.api.Result.Status;
23 import com.android.layoutlib.bridge.Bridge;
24 import com.android.layoutlib.bridge.impl.RenderSessionImpl;
25 
26 import android.os.Handler;
27 import android.os.Handler_Delegate;
28 import android.os.Message;
29 
30 import java.util.PriorityQueue;
31 import 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  */
45 public 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 
MessageBundle(Handler target, Message message, long uptimeMillis)52         MessageBundle(Handler target, Message message, long uptimeMillis) {
53             mTarget = target;
54             mMessage = message;
55             mUptimeMillis = uptimeMillis;
56         }
57 
58         @Override
compareTo(MessageBundle bundle)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 
AnimationThread(RenderSessionImpl scene, String threadName, IAnimationListener listener)72     public AnimationThread(RenderSessionImpl scene, String threadName,
73             IAnimationListener listener) {
74         super(threadName);
75         mSession = scene;
76         mListener = listener;
77     }
78 
preAnimation()79     public abstract Result preAnimation();
postAnimation()80     public abstract void postAnimation();
81 
82     @Override
run()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