AnimationThread.java revision 5a82d8c58bf91c357c37a82b9f5e5c26f676d847
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 17package com.android.layoutlib.bridge.impl; 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; 24 25import android.animation.ValueAnimator; 26import android.os.Handler; 27import android.os.Handler_Delegate; 28import android.os.Message; 29import android.os.Handler_Delegate.IHandlerCallback; 30 31import java.util.PriorityQueue; 32import 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 */ 46public 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 53 MessageBundle(Handler target, Message message, long uptimeMillis) { 54 mTarget = target; 55 mMessage = message; 56 mUptimeMillis = uptimeMillis; 57 } 58 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 Handler_Delegate.setCallback(new IHandlerCallback() { 87 public void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) { 88 if (msg.what == ValueAnimator.ANIMATION_START || 89 msg.what == ValueAnimator.ANIMATION_FRAME) { 90 mQueue.add(new MessageBundle(handler, msg, uptimeMillis)); 91 } else { 92 // just ignore. 93 } 94 } 95 }); 96 97 // call out to the pre-animation work, which should start an animation or more. 98 Result result = preAnimation(); 99 if (result.isSuccess() == false) { 100 mListener.done(result); 101 } 102 103 // loop the animation 104 RenderSession session = mSession.getSession(); 105 do { 106 // check early. 107 if (mListener.isCanceled()) { 108 break; 109 } 110 111 // get the next message. 112 MessageBundle bundle = mQueue.poll(); 113 if (bundle == null) { 114 break; 115 } 116 117 // sleep enough for this bundle to be on time 118 long currentTime = System.currentTimeMillis(); 119 if (currentTime < bundle.mUptimeMillis) { 120 try { 121 sleep(bundle.mUptimeMillis - currentTime); 122 } catch (InterruptedException e) { 123 // FIXME log/do something/sleep again? 124 e.printStackTrace(); 125 } 126 } 127 128 // check after sleeping. 129 if (mListener.isCanceled()) { 130 break; 131 } 132 133 // ready to do the work, acquire the scene. 134 result = mSession.acquire(250); 135 if (result.isSuccess() == false) { 136 mListener.done(result); 137 return; 138 } 139 140 // process the bundle. If the animation is not finished, this will enqueue 141 // the next message, so mQueue will have another one. 142 try { 143 // check after acquiring in case it took a while. 144 if (mListener.isCanceled()) { 145 break; 146 } 147 148 bundle.mTarget.handleMessage(bundle.mMessage); 149 if (mSession.render(false /*freshRender*/).isSuccess()) { 150 mListener.onNewFrame(session); 151 } 152 } finally { 153 mSession.release(); 154 } 155 } while (mListener.isCanceled() == false && mQueue.size() > 0); 156 157 mListener.done(Status.SUCCESS.createResult()); 158 159 } catch (Throwable throwable) { 160 // can't use Bridge.getLog() as the exception might be thrown outside 161 // of an acquire/release block. 162 mListener.done(Status.ERROR_UNKNOWN.createResult("Error playing animation", throwable)); 163 164 } finally { 165 postAnimation(); 166 Handler_Delegate.setCallback(null); 167 Bridge.cleanupThread(); 168 } 169 } 170} 171