Choreographer.java revision 5182c780a8b42acd46a06d693ab63a0dd78c6d70
1/*
2 * Copyright (C) 2011 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 android.view;
18
19import android.hardware.display.DisplayManagerGlobal;
20import android.os.Handler;
21import android.os.Looper;
22import android.os.Message;
23import android.os.SystemClock;
24import android.os.SystemProperties;
25import android.util.Log;
26import android.util.TimeUtils;
27
28import java.io.PrintWriter;
29
30/**
31 * Coordinates the timing of animations, input and drawing.
32 * <p>
33 * The choreographer receives timing pulses (such as vertical synchronization)
34 * from the display subsystem then schedules work to occur as part of rendering
35 * the next display frame.
36 * </p><p>
37 * Applications typically interact with the choreographer indirectly using
38 * higher level abstractions in the animation framework or the view hierarchy.
39 * Here are some examples of things you can do using the higher-level APIs.
40 * </p>
41 * <ul>
42 * <li>To post an animation to be processed on a regular time basis synchronized with
43 * display frame rendering, use {@link android.animation.ValueAnimator#start}.</li>
44 * <li>To post a {@link Runnable} to be invoked once at the beginning of the next display
45 * frame, use {@link View#postOnAnimation}.</li>
46 * <li>To post a {@link Runnable} to be invoked once at the beginning of the next display
47 * frame after a delay, use {@link View#postOnAnimationDelayed}.</li>
48 * <li>To post a call to {@link View#invalidate()} to occur once at the beginning of the
49 * next display frame, use {@link View#postInvalidateOnAnimation()} or
50 * {@link View#postInvalidateOnAnimation(int, int, int, int)}.</li>
51 * <li>To ensure that the contents of a {@link View} scroll smoothly and are drawn in
52 * sync with display frame rendering, do nothing.  This already happens automatically.
53 * {@link View#onDraw} will be called at the appropriate time.</li>
54 * </ul>
55 * <p>
56 * However, there are a few cases where you might want to use the functions of the
57 * choreographer directly in your application.  Here are some examples.
58 * </p>
59 * <ul>
60 * <li>If your application does its rendering in a different thread, possibly using GL,
61 * or does not use the animation framework or view hierarchy at all
62 * and you want to ensure that it is appropriately synchronized with the display, then use
63 * {@link Choreographer#postFrameCallback}.</li>
64 * <li>... and that's about it.</li>
65 * </ul>
66 * <p>
67 * Each {@link Looper} thread has its own choreographer.  Other threads can
68 * post callbacks to run on the choreographer but they will run on the {@link Looper}
69 * to which the choreographer belongs.
70 * </p>
71 */
72public final class Choreographer {
73    private static final String TAG = "Choreographer";
74    private static final boolean DEBUG = false;
75
76    // The default amount of time in ms between animation frames.
77    // When vsync is not enabled, we want to have some idea of how long we should
78    // wait before posting the next animation message.  It is important that the
79    // default value be less than the true inter-frame delay on all devices to avoid
80    // situations where we might skip frames by waiting too long (we must compensate
81    // for jitter and hardware variations).  Regardless of this value, the animation
82    // and display loop is ultimately rate-limited by how fast new graphics buffers can
83    // be dequeued.
84    private static final long DEFAULT_FRAME_DELAY = 10;
85
86    // The number of milliseconds between animation frames.
87    private static volatile long sFrameDelay = DEFAULT_FRAME_DELAY;
88
89    // Thread local storage for the choreographer.
90    private static final ThreadLocal<Choreographer> sThreadInstance =
91            new ThreadLocal<Choreographer>() {
92        @Override
93        protected Choreographer initialValue() {
94            Looper looper = Looper.myLooper();
95            if (looper == null) {
96                throw new IllegalStateException("The current thread must have a looper!");
97            }
98            return new Choreographer(looper);
99        }
100    };
101
102    // Enable/disable vsync for animations and drawing.
103    private static final boolean USE_VSYNC = SystemProperties.getBoolean(
104            "debug.choreographer.vsync", true);
105
106    // Enable/disable using the frame time instead of returning now.
107    private static final boolean USE_FRAME_TIME = SystemProperties.getBoolean(
108            "debug.choreographer.frametime", true);
109
110    // Set a limit to warn about skipped frames.
111    // Skipped frames imply jank.
112    private static final int SKIPPED_FRAME_WARNING_LIMIT = SystemProperties.getInt(
113            "debug.choreographer.skipwarning", 30);
114
115    private static final long NANOS_PER_MS = 1000000;
116
117    private static final int MSG_DO_FRAME = 0;
118    private static final int MSG_DO_SCHEDULE_VSYNC = 1;
119    private static final int MSG_DO_SCHEDULE_CALLBACK = 2;
120
121    // All frame callbacks posted by applications have this token.
122    private static final Object FRAME_CALLBACK_TOKEN = new Object() {
123        public String toString() { return "FRAME_CALLBACK_TOKEN"; }
124    };
125
126    private final Object mLock = new Object();
127
128    private final Looper mLooper;
129    private final FrameHandler mHandler;
130
131    // The display event receiver can only be accessed by the looper thread to which
132    // it is attached.  We take care to ensure that we post message to the looper
133    // if appropriate when interacting with the display event receiver.
134    private final FrameDisplayEventReceiver mDisplayEventReceiver;
135
136    private CallbackRecord mCallbackPool;
137
138    private final CallbackQueue[] mCallbackQueues;
139
140    private boolean mFrameScheduled;
141    private boolean mCallbacksRunning;
142    private long mLastFrameTimeNanos;
143    private long mFrameIntervalNanos;
144
145    /**
146     * Callback type: Input callback.  Runs first.
147     * @hide
148     */
149    public static final int CALLBACK_INPUT = 0;
150
151    /**
152     * Callback type: Animation callback.  Runs before traversals.
153     * @hide
154     */
155    public static final int CALLBACK_ANIMATION = 1;
156
157    /**
158     * Callback type: Traversal callback.  Handles layout and draw.  Runs last
159     * after all other asynchronous messages have been handled.
160     * @hide
161     */
162    public static final int CALLBACK_TRAVERSAL = 2;
163
164    private static final int CALLBACK_LAST = CALLBACK_TRAVERSAL;
165
166    private Choreographer(Looper looper) {
167        mLooper = looper;
168        mHandler = new FrameHandler(looper);
169        mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
170        mLastFrameTimeNanos = Long.MIN_VALUE;
171
172        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
173
174        mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
175        for (int i = 0; i <= CALLBACK_LAST; i++) {
176            mCallbackQueues[i] = new CallbackQueue();
177        }
178    }
179
180    private static float getRefreshRate() {
181        DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(
182                Display.DEFAULT_DISPLAY);
183        return di.refreshRate;
184    }
185
186    /**
187     * Gets the choreographer for the calling thread.  Must be called from
188     * a thread that already has a {@link android.os.Looper} associated with it.
189     *
190     * @return The choreographer for this thread.
191     * @throws IllegalStateException if the thread does not have a looper.
192     */
193    public static Choreographer getInstance() {
194        return sThreadInstance.get();
195    }
196
197    /**
198     * The amount of time, in milliseconds, between each frame of the animation.
199     * <p>
200     * This is a requested time that the animation will attempt to honor, but the actual delay
201     * between frames may be different, depending on system load and capabilities. This is a static
202     * function because the same delay will be applied to all animations, since they are all
203     * run off of a single timing loop.
204     * </p><p>
205     * The frame delay may be ignored when the animation system uses an external timing
206     * source, such as the display refresh rate (vsync), to govern animations.
207     * </p>
208     *
209     * @return the requested time between frames, in milliseconds
210     * @hide
211     */
212    public static long getFrameDelay() {
213        return sFrameDelay;
214    }
215
216    /**
217     * The amount of time, in milliseconds, between each frame of the animation.
218     * <p>
219     * This is a requested time that the animation will attempt to honor, but the actual delay
220     * between frames may be different, depending on system load and capabilities. This is a static
221     * function because the same delay will be applied to all animations, since they are all
222     * run off of a single timing loop.
223     * </p><p>
224     * The frame delay may be ignored when the animation system uses an external timing
225     * source, such as the display refresh rate (vsync), to govern animations.
226     * </p>
227     *
228     * @param frameDelay the requested time between frames, in milliseconds
229     * @hide
230     */
231    public static void setFrameDelay(long frameDelay) {
232        sFrameDelay = frameDelay;
233    }
234
235    /**
236     * Subtracts typical frame delay time from a delay interval in milliseconds.
237     * <p>
238     * This method can be used to compensate for animation delay times that have baked
239     * in assumptions about the frame delay.  For example, it's quite common for code to
240     * assume a 60Hz frame time and bake in a 16ms delay.  When we call
241     * {@link #postAnimationCallbackDelayed} we want to know how long to wait before
242     * posting the animation callback but let the animation timer take care of the remaining
243     * frame delay time.
244     * </p><p>
245     * This method is somewhat conservative about how much of the frame delay it
246     * subtracts.  It uses the same value returned by {@link #getFrameDelay} which by
247     * default is 10ms even though many parts of the system assume 16ms.  Consequently,
248     * we might still wait 6ms before posting an animation callback that we want to run
249     * on the next frame, but this is much better than waiting a whole 16ms and likely
250     * missing the deadline.
251     * </p>
252     *
253     * @param delayMillis The original delay time including an assumed frame delay.
254     * @return The adjusted delay time with the assumed frame delay subtracted out.
255     * @hide
256     */
257    public static long subtractFrameDelay(long delayMillis) {
258        final long frameDelay = sFrameDelay;
259        return delayMillis <= frameDelay ? 0 : delayMillis - frameDelay;
260    }
261
262    void dump(String prefix, PrintWriter writer) {
263        String innerPrefix = prefix + "  ";
264        writer.print(prefix); writer.println("Choreographer:");
265        writer.print(innerPrefix); writer.print("mFrameScheduled=");
266                writer.println(mFrameScheduled);
267        writer.print(innerPrefix); writer.print("mLastFrameTime=");
268                writer.println(TimeUtils.formatUptime(mLastFrameTimeNanos / 1000000));
269    }
270
271    /**
272     * Posts a callback to run on the next frame.
273     * <p>
274     * The callback runs once then is automatically removed.
275     * </p>
276     *
277     * @param callbackType The callback type.
278     * @param action The callback action to run during the next frame.
279     * @param token The callback token, or null if none.
280     *
281     * @see #removeCallbacks
282     * @hide
283     */
284    public void postCallback(int callbackType, Runnable action, Object token) {
285        postCallbackDelayed(callbackType, action, token, 0);
286    }
287
288    /**
289     * Posts a callback to run on the next frame after the specified delay.
290     * <p>
291     * The callback runs once then is automatically removed.
292     * </p>
293     *
294     * @param callbackType The callback type.
295     * @param action The callback action to run during the next frame after the specified delay.
296     * @param token The callback token, or null if none.
297     * @param delayMillis The delay time in milliseconds.
298     *
299     * @see #removeCallback
300     * @hide
301     */
302    public void postCallbackDelayed(int callbackType,
303            Runnable action, Object token, long delayMillis) {
304        if (action == null) {
305            throw new IllegalArgumentException("action must not be null");
306        }
307        if (callbackType < 0 || callbackType > CALLBACK_LAST) {
308            throw new IllegalArgumentException("callbackType is invalid");
309        }
310
311        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
312    }
313
314    private void postCallbackDelayedInternal(int callbackType,
315            Object action, Object token, long delayMillis) {
316        if (DEBUG) {
317            Log.d(TAG, "PostCallback: type=" + callbackType
318                    + ", action=" + action + ", token=" + token
319                    + ", delayMillis=" + delayMillis);
320        }
321
322        synchronized (mLock) {
323            final long now = SystemClock.uptimeMillis();
324            final long dueTime = now + delayMillis;
325            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
326
327            if (dueTime <= now) {
328                scheduleFrameLocked(now);
329            } else {
330                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
331                msg.arg1 = callbackType;
332                msg.setAsynchronous(true);
333                mHandler.sendMessageAtTime(msg, dueTime);
334            }
335        }
336    }
337
338    /**
339     * Removes callbacks that have the specified action and token.
340     *
341     * @param callbackType The callback type.
342     * @param action The action property of the callbacks to remove, or null to remove
343     * callbacks with any action.
344     * @param token The token property of the callbacks to remove, or null to remove
345     * callbacks with any token.
346     *
347     * @see #postCallback
348     * @see #postCallbackDelayed
349     * @hide
350     */
351    public void removeCallbacks(int callbackType, Runnable action, Object token) {
352        if (callbackType < 0 || callbackType > CALLBACK_LAST) {
353            throw new IllegalArgumentException("callbackType is invalid");
354        }
355
356        removeCallbacksInternal(callbackType, action, token);
357    }
358
359    private void removeCallbacksInternal(int callbackType, Object action, Object token) {
360        if (DEBUG) {
361            Log.d(TAG, "RemoveCallbacks: type=" + callbackType
362                    + ", action=" + action + ", token=" + token);
363        }
364
365        synchronized (mLock) {
366            mCallbackQueues[callbackType].removeCallbacksLocked(action, token);
367            if (action != null && token == null) {
368                mHandler.removeMessages(MSG_DO_SCHEDULE_CALLBACK, action);
369            }
370        }
371    }
372
373    /**
374     * Posts a frame callback to run on the next frame.
375     * <p>
376     * The callback runs once then is automatically removed.
377     * </p>
378     *
379     * @param callback The frame callback to run during the next frame.
380     *
381     * @see #postFrameCallbackDelayed
382     * @see #removeFrameCallback
383     */
384    public void postFrameCallback(FrameCallback callback) {
385        postFrameCallbackDelayed(callback, 0);
386    }
387
388    /**
389     * Posts a frame callback to run on the next frame after the specified delay.
390     * <p>
391     * The callback runs once then is automatically removed.
392     * </p>
393     *
394     * @param callback The frame callback to run during the next frame.
395     * @param delayMillis The delay time in milliseconds.
396     *
397     * @see #postFrameCallback
398     * @see #removeFrameCallback
399     */
400    public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
401        if (callback == null) {
402            throw new IllegalArgumentException("callback must not be null");
403        }
404
405        postCallbackDelayedInternal(CALLBACK_ANIMATION,
406                callback, FRAME_CALLBACK_TOKEN, delayMillis);
407    }
408
409    /**
410     * Removes a previously posted frame callback.
411     *
412     * @param callback The frame callback to remove.
413     *
414     * @see #postFrameCallback
415     * @see #postFrameCallbackDelayed
416     */
417    public void removeFrameCallback(FrameCallback callback) {
418        if (callback == null) {
419            throw new IllegalArgumentException("callback must not be null");
420        }
421
422        removeCallbacksInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN);
423    }
424
425    /**
426     * Gets the time when the current frame started.
427     * <p>
428     * This method provides the time in nanoseconds when the frame started being rendered.
429     * The frame time provides a stable time base for synchronizing animations
430     * and drawing.  It should be used instead of {@link SystemClock#uptimeMillis()}
431     * or {@link System#nanoTime()} for animations and drawing in the UI.  Using the frame
432     * time helps to reduce inter-frame jitter because the frame time is fixed at the time
433     * the frame was scheduled to start, regardless of when the animations or drawing
434     * callback actually runs.  All callbacks that run as part of rendering a frame will
435     * observe the same frame time so using the frame time also helps to synchronize effects
436     * that are performed by different callbacks.
437     * </p><p>
438     * Please note that the framework already takes care to process animations and
439     * drawing using the frame time as a stable time base.  Most applications should
440     * not need to use the frame time information directly.
441     * </p><p>
442     * This method should only be called from within a callback.
443     * </p>
444     *
445     * @return The frame start time, in the {@link SystemClock#uptimeMillis()} time base.
446     *
447     * @throws IllegalStateException if no frame is in progress.
448     * @hide
449     */
450    public long getFrameTime() {
451        return getFrameTimeNanos() / NANOS_PER_MS;
452    }
453
454    /**
455     * Same as {@link #getFrameTime()} but with nanosecond precision.
456     *
457     * @return The frame start time, in the {@link System#nanoTime()} time base.
458     *
459     * @throws IllegalStateException if no frame is in progress.
460     * @hide
461     */
462    public long getFrameTimeNanos() {
463        synchronized (mLock) {
464            if (!mCallbacksRunning) {
465                throw new IllegalStateException("This method must only be called as "
466                        + "part of a callback while a frame is in progress.");
467            }
468            return USE_FRAME_TIME ? mLastFrameTimeNanos : System.nanoTime();
469        }
470    }
471
472    private void scheduleFrameLocked(long now) {
473        if (!mFrameScheduled) {
474            mFrameScheduled = true;
475            if (USE_VSYNC) {
476                if (DEBUG) {
477                    Log.d(TAG, "Scheduling next frame on vsync.");
478                }
479
480                // If running on the Looper thread, then schedule the vsync immediately,
481                // otherwise post a message to schedule the vsync from the UI thread
482                // as soon as possible.
483                if (isRunningOnLooperThreadLocked()) {
484                    scheduleVsyncLocked();
485                } else {
486                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
487                    msg.setAsynchronous(true);
488                    mHandler.sendMessageAtFrontOfQueue(msg);
489                }
490            } else {
491                final long nextFrameTime = Math.max(
492                        mLastFrameTimeNanos / NANOS_PER_MS + sFrameDelay, now);
493                if (DEBUG) {
494                    Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
495                }
496                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
497                msg.setAsynchronous(true);
498                mHandler.sendMessageAtTime(msg, nextFrameTime);
499            }
500        }
501    }
502
503    void doFrame(long frameTimeNanos, int frame) {
504        final long startNanos;
505        synchronized (mLock) {
506            if (!mFrameScheduled) {
507                return; // no work to do
508            }
509
510            startNanos = System.nanoTime();
511            final long jitterNanos = startNanos - frameTimeNanos;
512            if (jitterNanos >= mFrameIntervalNanos) {
513                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
514                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
515                    Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
516                            + "The application may be doing too much work on its main thread.");
517                }
518                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
519                if (DEBUG) {
520                    Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
521                            + "which is more than the frame interval of "
522                            + (mFrameIntervalNanos * 0.000001f) + " ms!  "
523                            + "Skipping " + skippedFrames + " frames and setting frame "
524                            + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
525                }
526                frameTimeNanos = startNanos - lastFrameOffset;
527            }
528
529            if (frameTimeNanos < mLastFrameTimeNanos) {
530                if (DEBUG) {
531                    Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
532                            + "previously skipped frame.  Waiting for next vsync.");
533                }
534                scheduleVsyncLocked();
535                return;
536            }
537
538            mFrameScheduled = false;
539            mLastFrameTimeNanos = frameTimeNanos;
540        }
541
542        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
543        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
544        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
545
546        if (DEBUG) {
547            final long endNanos = System.nanoTime();
548            Log.d(TAG, "Frame " + frame + ": Finished, took "
549                    + (endNanos - startNanos) * 0.000001f + " ms, latency "
550                    + (startNanos - frameTimeNanos) * 0.000001f + " ms.");
551        }
552    }
553
554    void doCallbacks(int callbackType, long frameTimeNanos) {
555        CallbackRecord callbacks;
556        synchronized (mLock) {
557            // We use "now" to determine when callbacks become due because it's possible
558            // for earlier processing phases in a frame to post callbacks that should run
559            // in a following phase, such as an input event that causes an animation to start.
560            final long now = SystemClock.uptimeMillis();
561            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now);
562            if (callbacks == null) {
563                return;
564            }
565            mCallbacksRunning = true;
566        }
567        try {
568            for (CallbackRecord c = callbacks; c != null; c = c.next) {
569                if (DEBUG) {
570                    Log.d(TAG, "RunCallback: type=" + callbackType
571                            + ", action=" + c.action + ", token=" + c.token
572                            + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
573                }
574                c.run(frameTimeNanos);
575            }
576        } finally {
577            synchronized (mLock) {
578                mCallbacksRunning = false;
579                do {
580                    final CallbackRecord next = callbacks.next;
581                    recycleCallbackLocked(callbacks);
582                    callbacks = next;
583                } while (callbacks != null);
584            }
585        }
586    }
587
588    void doScheduleVsync() {
589        synchronized (mLock) {
590            if (mFrameScheduled) {
591                scheduleVsyncLocked();
592            }
593        }
594    }
595
596    void doScheduleCallback(int callbackType) {
597        synchronized (mLock) {
598            if (!mFrameScheduled) {
599                final long now = SystemClock.uptimeMillis();
600                if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
601                    scheduleFrameLocked(now);
602                }
603            }
604        }
605    }
606
607    private void scheduleVsyncLocked() {
608        mDisplayEventReceiver.scheduleVsync();
609    }
610
611    private boolean isRunningOnLooperThreadLocked() {
612        return Looper.myLooper() == mLooper;
613    }
614
615    private CallbackRecord obtainCallbackLocked(long dueTime, Object action, Object token) {
616        CallbackRecord callback = mCallbackPool;
617        if (callback == null) {
618            callback = new CallbackRecord();
619        } else {
620            mCallbackPool = callback.next;
621            callback.next = null;
622        }
623        callback.dueTime = dueTime;
624        callback.action = action;
625        callback.token = token;
626        return callback;
627    }
628
629    private void recycleCallbackLocked(CallbackRecord callback) {
630        callback.action = null;
631        callback.token = null;
632        callback.next = mCallbackPool;
633        mCallbackPool = callback;
634    }
635
636    /**
637     * Implement this interface to receive a callback when a new display frame is
638     * being rendered.  The callback is invoked on the {@link Looper} thread to
639     * which the {@link Choreographer} is attached.
640     */
641    public interface FrameCallback {
642        /**
643         * Called when a new display frame is being rendered.
644         * <p>
645         * This method provides the time in nanoseconds when the frame started being rendered.
646         * The frame time provides a stable time base for synchronizing animations
647         * and drawing.  It should be used instead of {@link SystemClock#uptimeMillis()}
648         * or {@link System#nanoTime()} for animations and drawing in the UI.  Using the frame
649         * time helps to reduce inter-frame jitter because the frame time is fixed at the time
650         * the frame was scheduled to start, regardless of when the animations or drawing
651         * callback actually runs.  All callbacks that run as part of rendering a frame will
652         * observe the same frame time so using the frame time also helps to synchronize effects
653         * that are performed by different callbacks.
654         * </p><p>
655         * Please note that the framework already takes care to process animations and
656         * drawing using the frame time as a stable time base.  Most applications should
657         * not need to use the frame time information directly.
658         * </p>
659         *
660         * @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
661         * in the {@link System#nanoTime()} timebase.  Divide this value by {@code 1000000}
662         * to convert it to the {@link SystemClock#uptimeMillis()} time base.
663         */
664        public void doFrame(long frameTimeNanos);
665    }
666
667    private final class FrameHandler extends Handler {
668        public FrameHandler(Looper looper) {
669            super(looper);
670        }
671
672        @Override
673        public void handleMessage(Message msg) {
674            switch (msg.what) {
675                case MSG_DO_FRAME:
676                    doFrame(System.nanoTime(), 0);
677                    break;
678                case MSG_DO_SCHEDULE_VSYNC:
679                    doScheduleVsync();
680                    break;
681                case MSG_DO_SCHEDULE_CALLBACK:
682                    doScheduleCallback(msg.arg1);
683                    break;
684            }
685        }
686    }
687
688    private final class FrameDisplayEventReceiver extends DisplayEventReceiver
689            implements Runnable {
690        private boolean mHavePendingVsync;
691        private long mTimestampNanos;
692        private int mFrame;
693
694        public FrameDisplayEventReceiver(Looper looper) {
695            super(looper);
696        }
697
698        @Override
699        public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
700            // Ignore vsync from secondary display.
701            // This can be problematic because the call to scheduleVsync() is a one-shot.
702            // We need to ensure that we will still receive the vsync from the primary
703            // display which is the one we really care about.  Ideally we should schedule
704            // vsync for a particular display.
705            // At this time Surface Flinger won't send us vsyncs for secondary displays
706            // but that could change in the future so let's log a message to help us remember
707            // that we need to fix this.
708            if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
709                Log.d(TAG, "Received vsync from secondary display, but we don't support "
710                        + "this case yet.  Choreographer needs a way to explicitly request "
711                        + "vsync for a specific display to ensure it doesn't lose track "
712                        + "of its scheduled vsync.");
713                scheduleVsync();
714                return;
715            }
716
717            // Post the vsync event to the Handler.
718            // The idea is to prevent incoming vsync events from completely starving
719            // the message queue.  If there are no messages in the queue with timestamps
720            // earlier than the frame time, then the vsync event will be processed immediately.
721            // Otherwise, messages that predate the vsync event will be handled first.
722            long now = System.nanoTime();
723            if (timestampNanos > now) {
724                Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
725                        + " ms in the future!  Check that graphics HAL is generating vsync "
726                        + "timestamps using the correct timebase.");
727                timestampNanos = now;
728            }
729
730            if (mHavePendingVsync) {
731                Log.w(TAG, "Already have a pending vsync event.  There should only be "
732                        + "one at a time.");
733            } else {
734                mHavePendingVsync = true;
735            }
736
737            mTimestampNanos = timestampNanos;
738            mFrame = frame;
739            Message msg = Message.obtain(mHandler, this);
740            msg.setAsynchronous(true);
741            mHandler.sendMessageAtTime(msg, timestampNanos / NANOS_PER_MS);
742        }
743
744        @Override
745        public void run() {
746            mHavePendingVsync = false;
747            doFrame(mTimestampNanos, mFrame);
748        }
749    }
750
751    private static final class CallbackRecord {
752        public CallbackRecord next;
753        public long dueTime;
754        public Object action; // Runnable or FrameCallback
755        public Object token;
756
757        public void run(long frameTimeNanos) {
758            if (token == FRAME_CALLBACK_TOKEN) {
759                ((FrameCallback)action).doFrame(frameTimeNanos);
760            } else {
761                ((Runnable)action).run();
762            }
763        }
764    }
765
766    private final class CallbackQueue {
767        private CallbackRecord mHead;
768
769        public boolean hasDueCallbacksLocked(long now) {
770            return mHead != null && mHead.dueTime <= now;
771        }
772
773        public CallbackRecord extractDueCallbacksLocked(long now) {
774            CallbackRecord callbacks = mHead;
775            if (callbacks == null || callbacks.dueTime > now) {
776                return null;
777            }
778
779            CallbackRecord last = callbacks;
780            CallbackRecord next = last.next;
781            while (next != null) {
782                if (next.dueTime > now) {
783                    last.next = null;
784                    break;
785                }
786                last = next;
787                next = next.next;
788            }
789            mHead = next;
790            return callbacks;
791        }
792
793        public void addCallbackLocked(long dueTime, Object action, Object token) {
794            CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
795            CallbackRecord entry = mHead;
796            if (entry == null) {
797                mHead = callback;
798                return;
799            }
800            if (dueTime < entry.dueTime) {
801                callback.next = entry;
802                mHead = callback;
803                return;
804            }
805            while (entry.next != null) {
806                if (dueTime < entry.next.dueTime) {
807                    callback.next = entry.next;
808                    break;
809                }
810                entry = entry.next;
811            }
812            entry.next = callback;
813        }
814
815        public void removeCallbacksLocked(Object action, Object token) {
816            CallbackRecord predecessor = null;
817            for (CallbackRecord callback = mHead; callback != null;) {
818                final CallbackRecord next = callback.next;
819                if ((action == null || callback.action == action)
820                        && (token == null || callback.token == token)) {
821                    if (predecessor != null) {
822                        predecessor.next = next;
823                    } else {
824                        mHead = next;
825                    }
826                    recycleCallbackLocked(callback);
827                } else {
828                    predecessor = callback;
829                }
830                callback = next;
831            }
832        }
833    }
834}
835