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