Home | History | Annotate | Download | only in animation
      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