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.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 
     53         MessageBundle(Handler target, Message message, long uptimeMillis) {
     54             mTarget = target;
     55             mMessage = message;
     56             mUptimeMillis = uptimeMillis;
     57         }
     58 
     59         @Override
     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             /* 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