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