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.Handler_Delegate.IHandlerCallback; 29 import android.os.Message; 30 31 import java.util.PriorityQueue; 32 import 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 */ 46 public 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 MessageBundle(Handler target, Message message, long uptimeMillis)53 MessageBundle(Handler target, Message message, long uptimeMillis) { 54 mTarget = target; 55 mMessage = message; 56 mUptimeMillis = uptimeMillis; 57 } 58 59 @Override compareTo(MessageBundle bundle)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 AnimationThread(RenderSessionImpl scene, String threadName, IAnimationListener listener)73 public AnimationThread(RenderSessionImpl scene, String threadName, 74 IAnimationListener listener) { 75 super(threadName); 76 mSession = scene; 77 mListener = listener; 78 } 79 preAnimation()80 public abstract Result preAnimation(); postAnimation()81 public abstract void postAnimation(); 82 83 @Override run()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